Add Encode / Decode Button for Voz

Add encode / decode hex, base64 button for Voz

// ==UserScript==
// @name         Add Encode / Decode Button for Voz
// @namespace    0x4076696e63766e
// @version      0.7
// @description  Add encode / decode hex, base64 button for Voz
// @author       0x4076696e63766e
// @match        https://voz.vn/t/*
// @grant        none
// @license      GPL-3.0
// ==/UserScript==
/*
 * Regex - Decode Script from https://greasyfork.org/en/scripts/504506-decode-hex-strings-on-voz
 */
(function () {
    'use strict';

    var $Editor = null;
    var $BBMode = false;
    function openDialog(encodeFunc, isDecode) {
        const input = prompt("Nhập vào văn bản để mã hóa:");
        if (input === null) return; // Người dùng đã hủy
        if(isDecode){
          var decoded = null;
          if(input.match(/\b([0-9A-Fa-f]{2}\s*){4,}\b/g)) decoded = decodeHex(input)
          if(input.match(/(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})/g)) decoded = decodeBase64(input)
          openPopup(decoded);
          return;
        }
        const encoded = encodeFunc(input);
        pickEditor();
        if ($Editor) {
            if($Editor.tagName.toString() === 'DIV'){
              $Editor.innerHTML += `<p>${encoded}</p>`;
            }else{
              $Editor.value += `\n${encoded}`;
            }
        }
    }
    function openPopup(decoded){
      const newDiv = document.createElement(`div`);
      newDiv.className = "overlay-container is-active";
      newDiv.innerHTML = `
        <div class="overlay" tabindex="-1" role="dialog" aria-hidden="false">
          <div class="overlay-title">
            <a class="overlay-titleCloser js-overlayClose" role="button" tabindex="0" aria-label="Close"></a>
            😤
          </div>
          <div class="overlay-content">
            <div class="block-container">
		          <div class="block-body">
                <textarea class="input" style="min-height: 100px; max-height: 602px; padding: 10px 20px 20px; break-word; resize: none; height: 100px;">${decoded}</textarea>
              </div>
            </div>
          </div>
        </div>
      `
      document.body.appendChild(newDiv);
      const closeButton = newDiv.querySelector('.js-overlayClose');
      closeButton.addEventListener('click', () => {
          document.body.removeChild(newDiv);
      });
    }
    function decodeHex(hexString) {
        hexString = hexString.replace(/\s+/g, '');
        if (!/^[0-9A-Fa-f]{2,}$/.test(hexString)) return hexString;
        let hexStr = '';
        try {
            for (let i = 0; i < hexString.length; i += 2) {
                hexStr += String.fromCharCode(parseInt(hexString.substr(i, 2), 16));
            }
            if (/^[\x20-\x7E]*$/.test(hexStr) && hexStr.length > 3) {
                return hexStr;
            } else {
                return hexString;
            }
        } catch {
            return hexString;
        }
    }

    function decodeBase64(base64String) {
        if (!/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/.test(base64String)) return base64String;
        try {
            let binaryString = atob(base64String);
            if (/^[\x20-\x7E]*$/.test(binaryString) && binaryString.length > 3) {
                return binaryString;
            } else {
                return base64String;
            }
        } catch {
            return base64String;
        }
    }
    function hexEncode(str) {
        return Array.from(str).map(char => char.charCodeAt(0).toString(16).padStart(2, '0')).join('');
    }

    function base64Encode(str) {
        return btoa(unescape(encodeURIComponent(str)));
    }

    function pickEditor() {
        const editorContainers = document.querySelectorAll('div.message-editorWrapper');
        editorContainers.forEach(container => {
            const textareaEditor = container.querySelector('textarea.input');
            const bbEditor = container.querySelector('div.fr-element');
            const bbCodeMode = container.querySelector('#xfBbCode-1');
            if(bbCodeMode) $BBMode = bbCodeMode.classList.contains('fr-active')
            $Editor = ($BBMode) ? textareaEditor : bbEditor
        });
    }

    function createButton(label, callback) {
        const button = document.createElement('div');
        button.className = 'button--primary button';
        button.style.marginRight = '5px';
        button.textContent = label;
        button.onclick = callback;
        return button;
    }

    function insertHelper(extraDivs) {
        if (document.querySelectorAll("div.encode-helper").length > 0) return;

        extraDivs.forEach(extraDiv => {
            const newDiv = document.createElement('div');
            newDiv.className = 'formButtonGroup encode-helper';
            newDiv.appendChild(createButton('Hex', () => openDialog(hexEncode)));
            newDiv.appendChild(createButton('Base64', () => openDialog(base64Encode)));
            newDiv.appendChild(createButton('Decode', () => openDialog(null, true)));
            extraDiv.parentNode.insertBefore(newDiv, extraDiv.nextSibling);
        });
    }

    function main() {
        const elements = document.querySelectorAll('div.formButtonGroup');
        insertHelper(elements);
    }

    function initializeObserver() {
        new MutationObserver(main).observe(document.documentElement, { childList: true, subtree: true });
    }

    document.readyState === 'loading'
        ? document.addEventListener('DOMContentLoaded', initializeObserver)
        : initializeObserver();
})();