ShowPass

Show password as plain text in password fields

Tính đến 09-11-2020. Xem phiên bản mới nhất.

// ==UserScript==
// @name         ShowPass
// @namespace    bitst0rm
// @version      0.0.6
// @description  Show password as plain text in password fields
// @author       bitst0rm
// @license      GPLv3
// @icon         https://github.com/bitst0rm-pub/ShowPass/raw/master/logo.png
// @include      *
// @run-at       document-start
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function(win) {
    'use strict';

    if (typeof GM_getValue === 'undefined' ||
        typeof GM_setValue === 'undefined' ||
        typeof GM_registerMenuCommand === 'undefined') {
        alert('You\'ve been warned 3 times that this script\n' +
            'does not support webshit aka Greasemonkey!\n' +
            'Get Tampermonkey and you will be happy.\n' +
            'Do not do this again.');
        return;
    }

    var APPID = 'showpass';
    var MOUSEOVER = 'idMouseOver';
    var DBLCLICK = 'idDblClick';
    var FOCUS = 'idFocus';
    var CTRL = 'idCtrl';
    var BOLD = 'idBold';
    var PREVIEW = 'idPreview';
    var PROGRESS = 'idProgress';
    var HEIGHT = 'idHeight';
    var COLOR = 'idColor';
    var EXCLSHOW = 'idExclShow';
    var EXCLSTRENGTH = 'idExclStrength';
    var DISTANCE = 'idDistance';
    var RANDOM = null;
    var HOST = null;

    var config = {};
    config[MOUSEOVER] = true;
    config[DBLCLICK] = false;
    config[FOCUS] = false;
    config[CTRL] = false;
    config[BOLD] = true;
    config[PROGRESS] = true;
    config[HEIGHT] = '6';
    config[COLOR] = '#ff0000';
    config[EXCLSHOW] = '^login\\.example\\.com\n^(.+?\\.)?example\\.net';
    config[EXCLSTRENGTH] = '^(.+?\\.)?dropbox\\.com';
    config[DISTANCE] = '^accounts\\.google\\.com#2\n^(.+?\\.)?(gmx\\.(net|at)|web\\.de)#1';
    for (var k in config) {
        if (GM_getValue(k) === undefined) {
            GM_setValue(k, config[k]);
        }
    }

    var html = '<!DOCTYPE html>' +
        '<html lang="en">' +
        '<head>' +
        '<title>ShowPass Option</title>' +
        '<meta charset="utf-8">' +
        '<meta name="viewport" content="width=device-width">' +
        '<style>' +
        'body{font-family:Arial,Helvetica,sans-serif;text-align:center;}' +
        'table{margin-right:auto;margin-left:auto;padding:1em;text-align:left;background-color:#f8f8f8;border:solid 1px #bebebe;border-radius:1em;}' +
        'td{padding:.5em;}' +
        'input,textarea,select{margin-left:0px;padding:4px;border-radius:3px;border:1px solid #9aa4b1;}' +
        'input[type="color"]{padding:1px 2px;}' +
        'textarea{min-width:20em;height:6em;}' +
        'label{margin:0 .4em;}' +
        '#' + HEIGHT + '{margin-left: .4em;}' +
        '.badge{background-color:#adebeb;border:1px solid #009999;border-radius:5px;padding:1px 6px;box-shadow:1px 1px 2px rgba(0,0,0,0.3);}' +
        '#msg{font-size:1.2em;font-weight:bold;margin:1em;transition:opacity .5s ease 1s;opacity:0;color:#669900;}' +
        '#msg.show{transition:none;opacity:1;}' +
        '</style>' +
        '</head>' +
        '<body>' +
        '<h1 id="' + APPID + '">ShowPass Option</h1>' +
        '<table><tr><td>Show password mode:</td>' +
        '<td><select id="menu">' +
        '<option id="' + MOUSEOVER + '"' + (GM_getValue(MOUSEOVER) ? ' selected' : '') + '>Mouse Over</option>' +
        '<option id="' + DBLCLICK + '"' + (GM_getValue(DBLCLICK) ? ' selected' : '') + '>Double Click</option>' +
        '<option id="' + FOCUS + '"' + (GM_getValue(FOCUS) ? ' selected' : '') + '>On Focus</option>' +
        '<option id="' + CTRL + '"' + (GM_getValue(CTRL) ? ' selected' : '') + '>Press CTRL Key</option>' +
        '</select></td></tr>' +
        '<tr><td>Password style:</td>' +
        '<td><input type="checkbox" id="' + BOLD + '"' + (GM_getValue(BOLD) ? ' checked' : '') + '>' +
        '<label for="' + BOLD + '">Bold</label>' +
        '<input type="color" value="' + GM_getValue(COLOR) + '" id="' + COLOR + '">' +
        '<label for="' + COLOR + '">Color</label></td></tr>' +
        '<tr><td>Show password strength:</td>' +
        '<td><input type="checkbox" id="' + PROGRESS + '"' + (GM_getValue(PROGRESS) ? ' checked' : '') + '>' +
        '<input type="number" id="' + HEIGHT + '" min="4" max="30" value="' + GM_getValue(HEIGHT) + '">' +
        '<label for="' + HEIGHT + '">px Height</label></td></tr>' +
        '<tr><td>Preview password:</td>' +
        '<td><input type="password" id="' + PREVIEW + '" placeholder="Enter password"></td></tr>' +
        '<tr><td>Exclude showing password:<br><i>(RegExp on hostnames)</i></td>' +
        '<td><textarea id="' + EXCLSHOW + '">' + GM_getValue(EXCLSHOW) + '</textarea></td></tr>' +
        '<tr><td>Exclude showing strength bar:<br><i>(RegExp on hostnames)</i></td>' +
        '<td><textarea id="' + EXCLSTRENGTH + '">' + GM_getValue(EXCLSTRENGTH) + '</textarea></td></tr>' +
        '<tr><td><span class="badge">Fine-turning</span> Distance between<br>strength bar and pw input field:<br><i>(RegExp hostname#nDistance)</i></td>' +
        '<td><textarea id="' + DISTANCE + '">' + GM_getValue(DISTANCE) + '</textarea></td></tr></table>' +
        '<div id="msg">Saved!</div>' +
        '</body>' +
        '</html>';

    var css = '.progress-bar-showpass{-webkit-appearance:none;-moz-appearance:none;appearance:none;height:' + GM_getValue(HEIGHT) + 'px;border:1px solid #b2b2b2;border-radius:1em;background-color:#efefef;}' +
        '.progress-bar-showpass::-webkit-progress-bar{border-radius:1em;background-color:#efefef;}' +
        '.progress-bar-showpass::-webkit-progress-value{-webkit-transition:width 0.5s ease;transition:width 0.5s ease;border-radius:1em 0 0 1em;}' +
        '.progress-bar-showpass[value="1"]::-webkit-progress-value{background-color:#dc3545;}' +
        '.progress-bar-showpass[value="2"]::-webkit-progress-value{background-color:#ffc107;}' +
        '.progress-bar-showpass[value="3"]::-webkit-progress-value{background-color:#1178f7;}' +
        '.progress-bar-showpass[value="4"]::-webkit-progress-value{background-color:#17a2b8;}' +
        '.progress-bar-showpass[value="5"]::-webkit-progress-value{border-radius:1em;background-color:#28a745;}' +
        '.progress-bar-showpass::-moz-progress-bar{-moz-transition:width 0.5s ease;border-radius:1em 0 0 1em;}' +
        '.progress-bar-showpass[value="1"]::-moz-progress-bar{background-color:#dc3545;}' +
        '.progress-bar-showpass[value="2"]::-moz-progress-bar{background-color:#ffc107;}' +
        '.progress-bar-showpass[value="3"]::-moz-progress-bar{background-color:#1178f7;}' +
        '.progress-bar-showpass[value="4"]::-moz-progress-bar{background-color:#17a2b8;}' +
        '.progress-bar-showpass[value="5"]::-moz-progress-bar{border-radius:1em;background-color:#28a745;}' +
        '#br-showpass{display:block;content:"";margin:0px;line-height:0px;}';

    if (GM_getValue(PROGRESS)) {
        GM_addStyle(css);
    }

    function blacklist(type) {
        var txt = GM_getValue(type);
        var lines = txt.split('\n');
        for (var i = 0; i < lines.length; i++) {
            var line = lines[i];
            var re = new RegExp(line);
            if (line && re.test(HOST)) return true;
        }
        return false;
    }

    function strength(pw) {
        return /.{8,}/.test(pw) * ( /* at least 8 characters */
            /.{12,}/.test(pw) + /* bonus if longer */
            /[a-z]/.test(pw) + /* a lower letter */
            /[A-Z]/.test(pw) + /* a upper letter */
            /\d/.test(pw) + /* a digit */
            /[^A-Za-z0-9]/.test(pw) /* a special character */
        );
    }

    function distance(input) {
        var txt = GM_getValue(DISTANCE);
        var lines = txt.split('\n');
        for (var i = 0; i < lines.length; i++) {
            var line = lines[i];
            var p = line.split('#');
            var hostname = p[0];
            var level = Number(p[1]);
            if (isNaN(level) || level < 1) {
                console.warn('[ShowPass] Distance must be of type number and greater than 0: ', line);
                continue;
            }
            var re = new RegExp(hostname);
            if (hostname && re.test(HOST) && !document.getElementById(APPID)) {
                var a = null;
                var b = null;
                var c = 0;
                var current = input;
                while (current.parentNode) {
                    if (c === level + 1) break;
                    b = current;
                    current = current.parentNode;
                    a = current;
                    c++;
                }
                if (a.constructor.name !== 'HTMLDocument') {
                    return [a, b];
                } else {
                    console.error('[ShowPass] Distance out of the scope: ', line);
                    return false;
                }
            }
        }
        return false;
    }

    function progress(input) {
        RANDOM = Math.random().toString(36).slice(2);
        input.style.setProperty('margin-bottom', '0px', 'important');
        var inputWidth = input.offsetWidth;

        var div = document.createElement('div');
        div.id = 'progress-wrapper-' + RANDOM;
        div.style.margin = '0px';
        div.style.padding = '0px';
        div.style.boxSizing = 'border-box';

        var br = document.createElement('br');
        br.id = 'br-showpass';

        var pgr = document.createElement('progress');
        pgr.id = 'progress-' + RANDOM;
        pgr.className = 'progress-bar-showpass';
        pgr.value = 0;
        pgr.max = 5;
        pgr.style.width = inputWidth + 'px';
        pgr.style.margin = '2px 0px 0px 0px';
        pgr.style.padding = '0px';
        pgr.style.verticalAlign = 'top';

        var arr = distance(input);
        if (arr) {
            arr[0].insertBefore(div, arr[1].nextSibling);
        } else {
            input.parentNode.insertBefore(div, input.nextSibling);
            div.appendChild(input);
            div.appendChild(br);
        }
        div.appendChild(pgr);

        input.addEventListener('keyup', function() {
            pgr.value = strength(input.value);
        });

        return pgr;
    }

    function update(input) {
        var pgr = progress(input);
        var c = 0;
        var check = setInterval(function() {
            if (c === 20) clearInterval(check);
            pgr.style.width = input.offsetWidth + 'px';
            c++;
        }, 100);
    }

    function cfg() {
        var display = true;
        document.body.parentElement.innerHTML = html;
        GM_addStyle(css);
        var pgrbox = document.getElementById(PROGRESS);
        var hight = document.getElementById(HEIGHT);
        hight.disabled = (pgrbox.checked ? false : true);

        document.body.addEventListener('change', function(e) {
            var t = e.target;
            GM_setValue(t.id, (t.type === 'checkbox' ? t.checked : t.value));
            if (t.id === 'menu') {
                for (var i = 0; i < t.length; i++) {
                    var option = t.options[i];
                    GM_setValue(option.id, option.selected);
                }
            }

            var pgr = document.getElementById('progress-' + RANDOM);

            if (t.id === PROGRESS) {
                if (!pgr) {
                    var input = document.getElementById(PREVIEW);
                    pgr = progress(input);
                    display = false;
                }
                if (!display) {
                    pgr.style.display = 'block';
                    hight.disabled = false;
                } else {
                    pgr.style.display = 'none';
                    hight.disabled = true;
                }
                display = !display;
            }

            if (t.id === HEIGHT) pgr.style.height = t.value + 'px';

            var elem = document.getElementById('msg');
            elem.classList.add('show');
            setTimeout(function() {
                elem.classList.remove('show');
            }, 100);
        });
    }

    GM_registerMenuCommand('Configure', cfg, 'C');
    HOST = win.location.hostname;
    if (blacklist(EXCLSHOW)) return;

    function setCaretToEnd(e) {
        setTimeout((function() {
            var t = e.target;
            var l = t.value.length;
            t.setSelectionRange(l, l);
        }), 0);
    }

    function show(e) {
        var t = e.target;
        t.type = 'text';
        t.style.setProperty('caret-color', 'black', 'important');
        t.style.setProperty('color', GM_getValue(COLOR), 'important');
        t.style.setProperty('font-weight', GM_getValue(BOLD) ? 'bold' : '', 'important');
        setCaretToEnd(e);
    }

    function hide(e) {
        var t = e.target;
        t.type = 'password';
        t.style.caretColor = '';
        t.style.color = '';
        t.style.fontWeight = '';
    }

    function init(body) {
        var isHide = true;
        var _toggle = function(e) {
            if (e.keyCode === 17) {
                if (isHide) {
                    show(e);
                } else {
                    hide(e);
                }
                isHide = !isHide;
            }
        };
        var _hide = function(e) {
            hide(e);
            isHide = true;
        };

        var inputs = body.querySelectorAll('input[type=password]');
        if (!inputs.length) return;

        for (var j = 0; j < inputs.length; j++) {
            var input = inputs[j];
            if (input.ariaHidden && input.ariaHidden === 'true' || input.hidden ||
                input.style.visibility && input.style.visibility === 'hidden' ||
                input.style.display && input.style.display === 'none') continue;
            if (!input.ready) {
                input.ready = true;
                if (GM_getValue(MOUSEOVER)) {
                    input.addEventListener('mouseover', show, false);
                    input.addEventListener('mouseout', hide, false);
                }
                if (GM_getValue(DBLCLICK)) {
                    input.addEventListener('dblclick', show, false);
                    input.addEventListener('blur', hide, false);
                }
                if (GM_getValue(FOCUS)) {
                    input.addEventListener('focus', show, false);
                    input.addEventListener('blur', hide, false);
                }
                if (GM_getValue(CTRL)) {
                    input.addEventListener('keyup', _toggle, false);
                    input.addEventListener('blur', _hide, false);
                }
                if (GM_getValue(PROGRESS)) {
                    if (blacklist(EXCLSTRENGTH)) continue;
                    update(input);
                }
            }
        }
    }

    var MutationObserver = win.MutationObserver || win.WebKitMutationObserver;
    var doc = win.document;
    if (MutationObserver) {
        var observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                var node;
                for (var i = 0; i < mutation.addedNodes.length; i++) {
                    node = mutation.addedNodes[i];
                    if (node.nodeName === 'IFRAME') {
                        var parser = document.createElement('a');
                        parser.href = node.src;
                        HOST = parser.hostname;

                        if (blacklist(EXCLSHOW)) continue;
                        if (node.contentDocument && node.contentDocument.body) init(node.contentDocument.body);
                    } else {
                        HOST = win.location.hostname;
                        if (doc && doc.body) init(doc.body);
                    }
                }
            });
        });

        observer.observe(doc.documentElement, {
            childList: true,
            subtree: true
        });
    }

    win.addEventListener('resize', function() {
        var bars = document.getElementsByClassName('progress-bar-showpass');
        for (var i = 0; i < bars.length; i++) {
            var bar = bars[i];
            var input = bar.parentNode.querySelector('input[type="password"]');
            bar.style.width = input.offsetWidth + 'px';
        }
    });
})(this);