Sakuto / HF Extensions

// ==UserScript==
// @name        HF Extensions
// @author      Sakuto
// @namespace   http://www.sakuto.be
// @description Add a lot of new functionality to improve experience on HackForums
// @include     *hackforums.net/*
// @version     0.57

// @grant       GM_xmlhttpRequest
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_notification
// @grant       GM_openInTab
// @grant       GM_addStyle
// @grant       GM_getResourceText

// @require     https://raw.github.com/sizzlemctwizzle/GM_config/master/gm_config.js
// @require     http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js
// @require     http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/highlight.min.js
// ==/UserScript==

// == To do List == 
// - Supprimer le tupple dans addink
// - Optimiser chargement des resultats via un buffer
// - Ignorer certaines catégories
// - Ignorer les sujets fermés
// - Ajout de template de post automatique
//      - Afficher les templates au dessus de la zone de post
// - Scammer database -> Application directement sur la userbar
// - Implémenter bouton de dons ?
// - Ajouter highlight de code
// == Fin To Do ==  

// - Add link to reputation given page

var predefinedStyle = "\
    #HFU_config {background:#333; color:#CCC; font-size:14px; } \
    #HFU_config_header {color:#FFF; padding-bottom: 20px;} \
    #HFU_config .section_desc {background:#072948; color:#FFF; border:none; font-size:14px;} \
    #HFU_config .section_header {display:none !important;} \
    #HFU_config .config_var {text-align:left;} \
    #HFU_config .field_label {font-size:14px; font-weight:normal} \
    #HFU_config * {font-family:Verdana, Arial, Sans-Serif; font-weight:normal}\
    #HFU_config label { display: inline-block; width: 600px;}\
    #HFU_config input[type=radio] { display: inline-block; }\
";

var linkList = [];

if(GM_getValue('ignoredSignature') == undefined) { GM_setValue('ignoredSignature', ['-1']); }
GM_config.init(
{
    'id': 'HFU_config',
    'title': 'HF Extensions - Configuration',
    'fields':
    {
        // Search pages
        'optimizeSearch':
        {
            'section': ['', 'Search'],
            'labelPos': 'left',
            'label': 'Optimize the search result page', 
            'type': 'checkbox', 
            'default': true
        },
        'numberOfPageToRetrieve':
        {
            'labelPos': 'left',
            'label': 'Number of Page To retrieve (5 maximum)', 
            'type': 'int', 
            'default': 4,
            'max': 5
        },
        'hideNoNewMessage':
        {
            'labelPos': 'left',
            'label': 'Hide thread wihout new message?', 
            'type': 'checkbox', 
            'default': false,
        },
        'hideMessageTitle':
        {
            'labelPos': 'left',
            'label': 'Hide the message title', 
            'type': 'checkbox', 
            'default': false,
        },
        // Reputation Option
        'boolReputationCheck':
        {
            'section': ['', 'Reputation'],
            'labelPos': 'left',
            'label': 'Get Notification when you receive a new rep ?',
            'type': 'checkbox',
            'default': true
        },
        'delayForReputationCheck':
        {
            'label': 'If precedent is check : Number of minutes between check on reputation',
            'labelPos': 'left',
            'type': 'int',
            'default': 2, 
            'min': 1
        },
        'addLinkToReputation':
        {
            'label': 'Do you want to add link to reputation message',
            'labelPos': 'left',
            'type': 'checkbox',
            'default': true
        },
        // Messages Options
        'addCounterForMessage':
        {
            'section': ['', 'Messages'],
            'labelPos': 'left',
            'label': 'Add a counter for the characters below textarea ?', 
            'type': 'checkbox', 
            'default': true
        },
        'quoteToPM':
        {
            'labelPos': 'left',
            'label': 'Add an option to send a PM with the quoted message',
            'type': 'checkbox',
            'default': false
        },
        'enableHighlight':
        {
            'labelPos': 'left',
            'label': 'Enable custom highlight for code tag',
            'type': 'checkbox',
            'default': true
        },
        'removeSignature':
        {
            'labelPos': 'left',
            'label': 'Allow removance of some users signature',
            'type': 'checkbox',
            'default': true
        }
    },
    'css': predefinedStyle
});

// Main function
$(function() {
    init();
    
    $("head").append('<link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/monokai.min.css" rel="stylesheet" type="text/css">');
    if(GM_config.get('enableHighlight')) {
        $('div.codeblock code').each(function(i, block) {
            hljs.highlightBlock(block);
        });    
    }
        
    // Add a counter below every textarea to show how many characters you writed
    if(GM_config.get('addCounterForMessage')) {
        if(window.location.href.indexOf('showthread') > 0) addCounterForCharacters('#message');    
        if(window.location.href.indexOf('newreply') > 0) addCounterForCharacters('#message_new', '.messageEditor');
    }

    // Send a notification when you receive new reputation
    if(GM_config.get('boolReputationCheck')) {
        if(dateObject.getTime() >= GM_getValue('reputationTimeout')) {            
            GM_setValue('reputationTimeout', dateObject.getTime() + 60 * GM_config.get('delayForReputationCheck'));
            
            checkReputation();
        }
    }

    // Add link to reputation message
    if(GM_config.get('addLinkToReputation')) {
        if(window.location.href.indexOf('reputation.php') > 0 || window.location.href.indexOf('repsgiven.php') > 0) {
            $("[class^=trow_reputation_]").each(function() {
                this.innerHTML = transformIntoUrl(this.innerHTML);
            });
        }
    }
    
    // Merge many pages of results as one and hide duplicate message
    if(GM_config.get('optimizeSearch')) {
        if(window.location.href.indexOf('action=results') > 0) {
            console.log("Start optimizing");

            if(document.referrer.indexOf('member.php') < 0) {
                console.log("Optimizing");
                optimizeSearch();
            }
        }
    }

    // Add a Quote To PM button
    if(GM_config.get('quoteToPM')) {
        if(window.location.href.indexOf('showthread.php') > 0) {
            addQuoteToPM();
        }
        
        if(window.location.href.indexOf('private.php?action=send') > 0) {
            if(GM_getValue('storedMessage') != null) {
                $('#message_new').val(GM_getValue('storedMessage'));
                GM_setValue('storedMessage', null);
            }
        }
    }

    // Remove some users signature
    if(GM_config.get('removeSignature')) {
        if(window.location.href.indexOf('showthread.php') > 0) {
            $('  <a href="#" class="bitButton removeSignature">Delete Sig</a>').appendTo('.post_management_buttons');  
            
            removeSignatureFromMessage();
        }
        
        $('.removeSignature').on('click', function(e) {
        	$selectedMessage = $(this).parent().parent().parent().parent();
        
        	var authorId  = getDataFromUrl('uid', $('tr:nth-child(2) strong .largetext a', $selectedMessage).attr('href'));
            var ignored = GM_getValue('ignoredSignature');
            ignored.push(authorId);
            GM_setValue('ignoredSignature', ignored);
            
            removeSignatureFromMessage();
            e.preventDefault();
        });
    }
});

/******************************/
/*     Main Function          */
/******************************/
function init() {
    $("#panel a:contains('Log Out')").after(' — <a href="#" id="HFUConfig">HFU Config</a>');

    dateObject = new Date();
        
    if(GM_getValue('userId') === undefined) {
        GM_setValue('userId', getDataFromUrl('uid', $("#panel a:first").attr('href')));
    }
    
    if(GM_getValue('reputationTimeout') === undefined) {
        GM_setValue('reputationTimeout', dateObject.getTime());    
    }

    // Capture click on configuration
    $("#HFUConfig").on('click', function(e) {
        GM_config.open(); 
        e.preventDefault();
    });
}

function addQuoteToPM() {
    $('<a href="#" class="bitButton quoteToPM">Quote to PM</a>').appendTo('.post_management_buttons');
    
    $('.quoteToPM').on('click', function(e) {
        $selectedMessage = $(this).parent().parent().parent().parent();
        
        var authorId  = getDataFromUrl('uid', $('tr:nth-child(2) strong .largetext a', $selectedMessage).attr('href'));
        GM_setValue('storedMessage', '[quote]' + $('tr:nth-child(3) .post_body', $selectedMessage).text().trim() + '[/quote]');
        
        GM_openInTab(window.location.origin + '/private.php?action=send&uid=' + authorId + '&subject=Answer%20to%20one%20of%20your%20message');
        e.preventDefault();
    });
}

function removeSignatureFromMessage() {
	$("#posts table").each(function() {
        var authorId  = getDataFromUrl('uid', $('tr:nth-child(2) strong .largetext a', this).attr('href'));
        if(authorId != undefined) {
            if(GM_getValue('ignoredSignature').indexOf(authorId) != -1) {
                $('.post_content div', this)[1].remove();
                $('.post_content hr', this).remove();
            }
        }
    });    
}

function optimizeSearch() {
    actualPage = (getDataFromUrl('page')) ? getDataFromUrl('page') : 1;
    promiseArray = [];
    
    $("table:nth-child(2) tr:gt(1)").empty();
    
    for(var page = ((actualPage*GM_config.get('numberOfPageToRetrieve')) - GM_config.get('numberOfPageToRetrieve')); page < actualPage * GM_config.get('numberOfPageToRetrieve'); page++) {
        promiseArray.push(retrieveContentFromUrl(window.location.href + "&page=" + page));
    }

    Promise.all(promiseArray).then(function(results) {
        for(var page = 0; page < results.length; page++) {
            retrieveLinkFromContent($(results[page]));    
        }  
    });
}

// Retrieve all the link from a content given.
function retrieveLinkFromContent(content, adresse) {
    var clonedLink;
    
    $("table:nth-child(2) tr:gt(1)", content).each(function() {
        clonedLink = $(this).clone();    
        this.remove();
        
        if(addList($("td:nth-child(3) a:nth-child(1)", this).text(), $("td:nth-child(3) a:nth-child(1)", this).attr('href')) === 0) {
            if($(" > * ", clonedLink).length > 1) {  
                                
                if(GM_config.get("hideNoNewMessage")) {
                     if($("td:nth-child(1) img", this).attr('src') != "http://hackforums.net/images/modern_bl/dot_folder.gif")      
                         if($("td:nth-child(1) img", this).attr('src') != "http://hackforums.net/images/modern_bl/dot_lockfolder.gif")
                             if($("td:nth-child(1) img", this).attr('src') != "http://hackforums.net/images/modern_bl/dot_hotfolder.gif")
                                 $("table:nth-child(2)").append(changeLinkToLastPost(clonedLink));
                } else {
                    $("table:nth-child(2)").append(changeLinkToLastPost(clonedLink));
                }                
            }
        }
    });
}

// Add a link to the list if it's not yet in it.
function addList(title, link) {
    for(var j = 0; j < linkList.length; j++) {
        if(linkList[j].urlTitle === title) {
            return 1;   
        }
    }
    
    linkList.push({urlTitle: title, urlLink: link});
    return 0;
}

// Return Promise of the content of the passed URL
function retrieveContentFromUrl(url) {
    return new Promise(function(resolve, request) {
        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function(response) {
                resolve(response.responseText);
            }
        });    
    });
}

function checkReputation() {
    var reputationPage = retrieveContentFromUrl(window.location.origin + "/reputation.php?uid=" + GM_getValue('userId'), 1);
    reputationPage.then(function(result) {
        var lastReputationReceived = $("td [class^=trow_reputation_]", result).first().parent().parent();
        
        var userReputation   = $(".repvoteleft", lastReputationReceived).text();
        var reasonReputation = $(".repvotemid", lastReputationReceived).text();
        var dateReputation   = $(".repvoteright", lastReputationReceived).text();
        
        if(GM_getValue('lastReputation') != reasonReputation && reasonReputation != "") {
            GM_notification(reasonReputation + "\r\n" + dateReputation, "You received a new Reputation from " + userReputation, null, function() { GM_openInTab(window.location.origin +"/reputation.php?uid=" + GM_getValue('userId')) });
            GM_setValue('lastReputation', reasonReputation);
        }
    });
}

function addCounterForCharacters(textarea, divToAppend) {
    divToAppend = (divToAppend == null) ? textarea : divToAppend;
    $(divToAppend).after('<div id="messageCounter"><span>0</span>/25 Characteres needed</div><br>');
    
    $(textarea).on('keyup', function(e) {
        $("#messageCounter span").html(this.value.length);   
    });    
}

/******************************/
/*     Global Function        */
/******************************/

// Add the lastPost action to the url on the node passed
function changeLinkToLastPost(elementNode) {
    if(GM_config.get("hideMessageTitle")) {
        var threadTitle = $("td:nth-child(3) span", elementNode).html();
        
        if(threadTitle != null) {
            threadTitle = threadTitle.split("<br>").shift();
            
            $("td:nth-child(3) span", elementNode).html("");
            $("td:nth-child(3)", elementNode).html(threadTitle);
        }
    }
    
    var _href = $("td:nth-child(3) a:nth-child(1)", elementNode).attr('href');
    $("td:nth-child(3) a:nth-child(1)", elementNode).attr('href', _href + "&action=lastpost");
    return elementNode;
}

// Allow you to retrieve get parameter from URL
function getDataFromUrl(VarSearch, url){
    var SearchString = (url == null) ? window.location.search.substring(1) : url;
    
    var VariableArray = SearchString.split('&');
    for(var i = 0; i < VariableArray.length; i++){
        var KeyValuePair = VariableArray[i].split('=');
        if(KeyValuePair[0] == VarSearch){
            return KeyValuePair[1];
        }
    }
}

// Detect and transform into a clickable link every valid URL in a passed content
function transformIntoUrl(text) {
    if(text.indexOf('img src') > 0) {
        return text;    
    }
    
    var urlRegex = /(https?:\/\/[^\s]+)/g;
    return text.replace(urlRegex, '<a href="$1">$1</a>');
}