Encode/Decode Base64 or Hex with Copy Option

Encode or decode Base64 or Hex strings and copy to clipboard

נכון ליום 25-08-2024. ראה הגרסה האחרונה.

  // ==UserScript==
  // @name         Encode/Decode Base64 or Hex with Copy Option
  // @namespace    http://tampermonkey.net/
  // @version      1.6
  // @description  Encode or decode Base64 or Hex strings and copy to clipboard
  // @author       itisme
  // @include      https://*
  // @include      http://*
  // @icon         https://data.voz.vn/styles/next/xenforo/smilies/popopo/sexy_girl.png?v=01
  // @license      GPL-3.0
  // @grant        GM_addStyle
  // ==/UserScript==

  (function() {
      'use strict';
      GM_addStyle(`
           :root {
              --overlay-bg: rgba(0, 0, 0, 0.6);
              --overlay-color: #fff;
              --font-color: #333;
              --close-color: #f44336;
              --close-hover-color: #d32f2f;
              --button-bg: #007bff;
              --button-active-bg: #0056b3;
              --copy-bg: #28a745;
              --copy-active-bg: #195427;
              --copy-hover-bg: #218838;
              --clear-bg: #dc3545;
              --clear-active-bg: #c82333;
              --shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
              --transition: 0.3s ease;
          }

          .my-overlay-container {
              position: fixed;
              top: 0;
              left: 0;
              width: 100%;
              height: 100%;
              background: var(--overlay-bg);
              display: flex;
              justify-content: center;
              align-items: center;
              z-index: 9999;
              max-height: vh;
              overflow: auto;
          }

          .my-overlay {
              background: var(--overlay-color);
              padding: 20px;
              border-radius: 8px;
              width: 100%;
              max-width: 600px;
              position: relative;
              box-shadow: var(--shadow);
              transition: var(--transition);
          }

          .my-overlay-title {
              display: flex;
              justify-content: space-between;
              align-items: center;
              margin-bottom: 15px;
              position: relative;
          }

          .my-overlay-title-text {
              font-size: 24px;
              font-weight: 600;
              color: var(--font-color);
          }

          .my-overlay-titleCloser {
              font-size: 30px;
              cursor: pointer;
              color: var(--close-color);
              transition: var(--transition);
          }

          .my-overlay-titleCloser:hover {
              color: var(--close-hover-color);
          }

          .my-overlay-content {
              display: flex;
              flex-direction: column;
          }

          .my-overlay-content label {
              margin-bottom: 5px;
              font-weight: bold;
          }

          .my-overlay-content select,
          .my-overlay-content textarea {
              width: 100%;
              margin-bottom: 15px;
              padding: 10px;
              border: 1px solid #ccc;
              border-radius: 5px;
              box-sizing: border-box;
          }

          .my-overlay-content textarea {
              height: 100px;
              font-size: 14px;
              line-height: 1.4;
              resize: vertical;
          }

          .my-button-container {
              display: flex;
              gap: 15px;
          }

          .my-button-container button {
              padding: 12px 20px;
              font-size: 14px;
              border: none;
              border-radius: 5px;
              cursor: pointer;
              flex: 1;
              transition: background-color var(--transition), box-shadow var(--transition);
              box-shadow: var(--shadow);
          }

          #actionButton {
              background-color: var(--button-bg);
              color: #fff;
          }

          #actionButton:hover,
          #actionButton:active {
              background-color: var(--button-active-bg);
          }

          #copyButton {
              background-color: var(--copy-bg);
              color: #fff;
          }

          #copyButton:hover {
              background-color: var(--copy-hover-bg);
          }

          #copyButton:active {
              background-color: var(--copy-active-bg);
          }

          #copyButton.flash {
              animation: flash 1s;
              background-color: var(--copy-bg);
          }

          @keyframes flash {
              0% { background-color: var(--copy-bg); }
              100% { background-color: var(--copy-active-bg); }
          }

          .custom-popup-link {
              position: fixed;  /* Đảm bảo nút luôn nằm cố định trên màn hình */
              width: 40px;
              height: 40px;
              background-image: url('https://data.voz.vn/styles/next/xenforo/smilies/popopo/sexy_girl.png?v=01');
              background-size: cover;
              background-position: center;
              border: none;
              cursor: pointer;
              z-index: 10000;
              user-select: none;
              background-color: transparent; /* Đảm bảo nút không có màu nền */
              top: 20px;  /* Cách từ trên xuống 20px */
              right: 20px; /* Cách từ phải vào 20px */
          }

      `);

      const createToggleButton = () => {
          const toggleButton = document.createElement('button');
          toggleButton.className = 'custom-popup-link';
          document.body.appendChild(toggleButton);

          // Load saved position
          const savedPosition = JSON.parse(localStorage.getItem('toggleButtonPosition')) || { top: 10, left: 10 };
          toggleButton.style.top = `${savedPosition.top}px`;
          toggleButton.style.left = `${savedPosition.left}px`;

          makeDraggable(toggleButton);
      };

      const makeDraggable = (element) => {
          if (!element) return console.error('Phần tử không tồn tại.');

          let offsetX, offsetY, startX, startY, isDragging = false;

          const moveElement = (e) => {
              if (!isDragging && (Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5)) {
                  isDragging = true;
              }
              if (isDragging) {
                  element.style.left = `${e.clientX - offsetX}px`;
                  element.style.top = `${e.clientY - offsetY}px`;
              }
          };

          const stopDragging = (e) => {
              if (isDragging) {
                  localStorage.setItem('toggleButtonPosition', JSON.stringify({
                      top: parseInt(element.style.top, 10),
                      left: parseInt(element.style.left, 10)
                  }));
              } else if (e.button === 0) {
                  toggleOverlay();
              }
              document.removeEventListener('mousemove', moveElement);
              document.removeEventListener('mouseup', stopDragging);
              isDragging = false;
          };

          element.addEventListener('mousedown', (e) => {
              if (e.button !== 0) return;
              offsetX = e.clientX - element.offsetLeft;
              offsetY = e.clientY - element.offsetTop;
              startX = e.clientX;
              startY = e.clientY;
              document.addEventListener('mousemove', moveElement);
              document.addEventListener('mouseup', stopDragging);
          });
      };

      const toggleOverlay = () => {
          const overlayContainer = document.querySelector('.my-overlay-container');
          if (overlayContainer) {
              overlayContainer.style.display = (overlayContainer.style.display === 'none') ? 'flex' : 'none';
          } else {
              if (typeof createOverlay === 'function') {
                  createOverlay();
                  const newOverlayContainer = document.querySelector('.my-overlay-container');
                  if (newOverlayContainer) {
                      newOverlayContainer.style.display = 'flex';
                  } else {
                      console.error('Không thể tìm thấy phần tử .my-overlay-container sau khi tạo.');
                  }
              } else {
                  console.error('Hàm createOverlay không tồn tại.');
              }
          }
      };

      const createOverlay = () => {
          const overlayContainer = document.createElement('div');
          overlayContainer.className = 'my-overlay-container';
          overlayContainer.innerHTML = `
              <div class="my-overlay">
                  <div class="my-overlay-title">
                      <span class="my-overlay-title-text">Encode/Decode Input</span>
                      <a class="my-overlay-titleCloser" role="button" aria-label="Close">×</a>
                  </div>
                  <div class="my-overlay-content">
                      <label>Select Operation:</label>
                      <select id="operationType">
                          <option value="decode">Decode</option>
                          <option value="encode">Encode</option>
                      </select>
                      <label>Select Type:</label>
                      <select id="decodeType">
                          <option value="hex">Hex</option>
                          <option value="base64">Base64</option>
                          <option value="url">URL</option>
                      </select>
                      <textarea id="inputString" placeholder="Enter text..."></textarea>
                      <textarea id="resultOutput" readonly placeholder="Result..."></textarea>
                      <div class="my-button-container">
                          <button id="actionButton">Decode/Encode</button>
                          <button id="copyButton">Copy</button>
                      </div>
                  </div>
              </div>
          `;
          document.body.appendChild(overlayContainer);

          const closeButton = document.querySelector('.my-overlay-titleCloser');
          closeButton.addEventListener('click', () => {
              overlayContainer.style.display = 'none';
              document.getElementById('inputString').value = '';
              document.getElementById('resultOutput').value = '';
              document.getElementById('operationType').value = "decode";
              document.getElementById('decodeType').value = "hex";
          });

          const encodeHex = s => Array.from(s, c => ('0' + c.charCodeAt(0).toString(16)).slice(-2)).join(' ').toUpperCase();
          const decodeHex = h => h.replace(/\s+/g, '').match(/^[0-9A-Fa-f]+$/) ? h.match(/../g).map(b => String.fromCharCode(parseInt(b, 16))).join('') : h;
          const decodeBase64 = b => /^[A-Za-z0-9+/]+={0,2}$/.test(b) ? atob(b) : b;
          const encodeBase64 = b => btoa(b);

          document.getElementById('actionButton').addEventListener('click', () => {
              const op = document.getElementById('operationType').value;
              const type = document.getElementById('decodeType').value;
              const input = document.getElementById('inputString').value.trim();
              if (!input) return alert('Vui lòng nhập chuỗi.');

              let result;
              try {
                  result = type === 'base64' ? (op === 'decode' ? decodeBase64(input) : encodeBase64(input)) :
                           type === 'hex' ? (op === 'decode' ? decodeHex(input) : encodeHex(input)) :
                           (op === 'decode' ? decodeURIComponent(input) : encodeURIComponent(input));

                  if (result === input) throw new Error('Invalid input');
                  document.getElementById('resultOutput').value = result;
              } catch {
                  alert('Lỗi: Chuỗi không hợp lệ.');
              }
          });

          document.getElementById('copyButton').addEventListener('click', () => {
              const resultOutput = document.getElementById('resultOutput');
              const textToCopy = resultOutput.value.trim();

              if (!textToCopy) {
                  alert('Không có gì để sao chép!');
                  return;
              }

              navigator.clipboard.writeText(textToCopy)
                  .then(() => {
                      alert('Đã sao chép vào clipboard!');
                      const copyButton = document.getElementById('copyButton');
                      copyButton.classList.add('flash');
                      setTimeout(() => copyButton.classList.remove('flash'), 1000);
                  })
                  .catch(err => {
                      console.error('Error copying to clipboard:', err);
                      alert('Lỗi khi sao chép vào clipboard.');
                  });
          });
      };

      createToggleButton();
  })();