DrDoof / HF Character Counter

// ==UserScript==
// @name            HF Character Counter
// @description     Counts the number of characters in a post or PM on Hackforums.net!
// @author          Josef A. (DrDoof)
// @namespace       https://github.com/josefandersson
// @require         https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// @include         *://hackforums.net/*
// @grant           GM_addStyle
// @grant           GM_setValue
// @grant           GM_getValue
// @version         1.2.7
// @run-at          document-end
// @updateURL       https://openuserjs.org/meta/DrDoof/HF_Character_Counter.meta.js
// ==/UserScript==



/* HELLO USER - CHANGE THESE SETTINGS AS YOU WILL - ALL SHOULD BE SET TO TRUE FOR THE REAL CHARACTER COUNT (THE ONE USED FOR LUCKY AWARD) */
ignore = {
    quotes:     true, // Ignore quotes
    images:     true, // Ignore images
    urls:       true, // Ignore urls
    emails:     true, // Ignore emails
    fonts:      true, // Ignore fonts
    colors:     true, // Ignore colors
    aligns:     true, // Ignore aligns
    sizes:      true, // Ignore sizes
    lists:      true, // Ignore lists
    italics:    true, // Ignore italics
    bolds:      true, // Ignore bolds
    underlines: true, // Ignore underlines
    strikes:    true, // Ignore strikes
    spoilers:   true, // Ignore spoilers
    smilies:    true, // Ignore smilies
    codes:      true, // Ignore codes
    phps:       true, // Ignore phps
    videos:     true, // Ignore videos
    newlines:   true, // Ignore newlines
};
/* END OF SETTINGS*/



// To get the real length of a textarea we have to think like the HF server!
sr = /^([ ]|\n|\t|\r)+/g;
er = /([ ]|\n|\t|\r)+$/g;

// The user may also toggle the counting of various things.
qr  = /\[quote.*?\](.|\n)*?\[\/quote\]/g;                             // quotes
ir  = /\[img\](http|https):\/\/.+\[\/img\]/g;                         // images
ur1 = /\[url\].+\[\/url\]/g;                                          // 1st url variant
ur2 = /\[url=.+\]((?:.|\n)*?)\[\/url\]/g;     // 2nd url variant
mr1 = /\[email\].+\[\/email\]/g;                                      // 1st email variant
mr2 = /\[email=.+\]((?:.|\n)*?)\[\/email\]/g; // 2nd email variant
fr  = /\[font=.+\]((?:.|\n)*?)\[\/font\]/g;   // font
cr  = /\[color=#[a-fA-F0-9]{6}\]((?:.|\n)*?)\[\/color\]/g;                                        // color
ar  = /\[align=(?:left|right|center|justify)\]((?:.|\n)*?)\[\/align\]/g;                          // align
sir = /\[size=(?:xx-small|x-small|small|medium|large|x-large|xx-large)\]((?:.|\n)*?)\[\/size\]/g; // size
lr  = /\[list(?:=1|=a)?\]((?:.|\n)*?)\[\/list\]/g;                // list
br  = /\[b\]((?:.|\n)*?)\[\/b\]/g;                                                       // bold
itr = /\[i\]((?:.|\n)*?)\[\/i\]/g;                                // italic
ur  = /\[u\]((?:.|\n)*?)\[\/u\]/g;                                // underline
str = /\[s\]((?:.|\n)*?)\[\/s\]/g;                                // strikethrough
cor = /\[code\]((?:.|\n)*?)\[\/code\]/g;                          // code
pr = /\[php\]((?:.|\n)*?)\[\/php\]/g;                             // php
vr = /\[video=(?:youtube|vimeo|yahoo)\]((?:.|\n)*?)\[\/video\]/g; // video (I believe only three of them works)
sr1 = /\[sp.*?\]((?:.|\n)*?)\[\/sp\]/g;                           // 1st spoiler variant
sr2 = /\[spoiler.*?\]((?:.|\n)*?)\[\/spoiler\]/g;                 // 2nd spoiler variant
smr = /:(pinch|victoire|hehe|oui|bebe-pleure|ohmy|blink|superman|nono|biggrin|sad|unsure|glare|roflmao|devlish|rolleyes|cool|gratte|confused|blackhat|ninja|blush|lipssealed|yeye|non|smile|whistle|sleep|evilgrin|omg|tongue|mad|huh|thumbsup|wacko|pirate):/g; // smilies
nr = /\n*/g; // newline

// Our textarea variable will just default to null.
ta = null;

// Whether or not we should ignore to filter ignore settings.
// If you are using longer posts this should probably be toggled to true to prevent lag.
// This gets toggled by clicking on the character counter text.
f = GM_getValue('f') === 'true';

/* Find the textarea for this page, add the counter
** element to it and add the eventlistener. */
(() => {
    // Add the styling for the display text.
    GM_addStyle([
        'p#char_count {',
            'margin-top: -20px;',
            'float: right;',
            'margin-right: 13px;',
            'color: red;',
            'opacity: 0.999', // This one has to be here but it makes absolutely no sense why... Stupid CSS.
        '}'
    ].join(''));

    // Determine the textarea from current path.
    paths = {'/showthread.php': [25, 100], '/newreply.php': [25, 100], '/newthread.php': [25, false]};
    if (paths[location.pathname]) {
        ta = $('#message_new');
        if (!ta.length) ta = $('#message');
        low = paths[location.pathname][0];
        high = paths[location.pathname][1];
    }

    // Whoops! No text areas on this page!
    if (!ta) return;

    // Add the element that displays the character count text after the text area.
    (cc = $('<p>', { id: 'char_count', text: `${ta.text().length} characters`, onselectstart: 'return false;' })).insertAfter(ta);

    // Update the character counter elements text and color.
    u = () => {
        if (high === false) {
            cc.text(`${rl} characters`);
            if (low <= rl) cc.css('color', 'green'); else cc.css('color', 'red');
        } else {
            if (f) {
                cc.text(`${rl} characters (click to toggle lucky filtering)`);
                if (low <= rl) cc.css('color', 'green'); else cc.css('color', 'red');
            } else {
                if (l == rl) cc.text(`${rl} characters`);
                else         cc.text(`${rl} characters (${l} lucky characters)`);
                if (high <= l) cc.css('color', 'green'); else if (low <= rl) cc.css('color', '#008ae6'); else cc.css('color', 'red');
            }
        }
    };

    // Count the characters in the text right now.
    lf = () => {
        t = ta.val(), t = t.slice((t.match(sr) || [''])[0].length, -(t.match(er) || [''])[0].length || undefined); // Remove spaces at start and end of string and replace newlines with two underscores.

        // This will be the character count before user filtering has been applied.
        rl = t.split("\n").join('__').length;

        // Don't filter ignore settings when f is true.
        if (!f && high !== false) {
            // Depending on the user settings we'll remove some things from the text.
            if (ignore.newlines)   t = t.replace(nr, '');
            if (ignore.quotes)     t = t.replace(qr, '');
            if (ignore.images)     t = t.replace(ir, '');
            if (ignore.urls)       { t = t.replace(ur1, ''); while (ur2.test(t)) t = t.replace(ur2, '$1'); }
            if (ignore.emails)     { t = t.replace(mr1, ''); while (mr2.test(t)) t = t.replace(mr2, '$1'); }
            if (ignore.fonts)      while (fr.test(t)) t = t.replace(fr, '$1');
            if (ignore.colors)     while (cr.test(t)) t = t.replace(cr, '$1');
            if (ignore.aligns)     while (ar.test(t)) t = t.replace(ar, '$1');
            if (ignore.sizes)      while (sir.test(t)) t = t.replace(sir, '$1');
            if (ignore.lists)      while (lr.test(t)) t = t.replace(lr, '$1');
            if (ignore.italics)    while (itr.test(t)) t = t.replace(itr, '$1');
            if (ignore.bolds)      while (br.test(t)) t = t.replace(br, '$1');
            if (ignore.underlines) while (ur.test(t)) t = t.replace(ur, '$1');
            if (ignore.strikes)    while (str.test(t)) t = t.replace(str, '$1');
            if (ignore.spoilers)   { while (sr1.test(t)) t = t.replace(sr1, '$1'); while (sr2.test(t)) t = t.replace(sr2, '$1'); }
            if (ignore.smilies)    t = t.replace(smr, '');
            if (ignore.codes)      t = t.replace(cor, '');
            if (ignore.phps)       t = t.replace(pr, '');
            if (ignore.videos)     t = t.replace(vr, '');

            // The length after filtering ignore settings.
            l = t.length;
        }
    }

    // Listen to every click event on the character counter element.
    cc.click((e) => {
        f = !f;
        GM_setValue('f', `${f}`); // Save the state between pages.
        lf();
        u();
    });

    // Listen to every input event in the textarea and update the character counter element.
    ta.on('input', (e) => {
        // Count the characters in the text.
        lf();

        // Update the character counter elements text and color.
        u();
    });
})();