Base64 文本解码器

选中文本时自动检测并解码 Base64 编码的内容,支持一键复制解码结果

// ==UserScript==
// @name         Base64 文本解码器
// @name:en      Base64 Text Decoder
// @namespace    https://github.com/eep
// @version      1.0.2
// @description  选中文本时自动检测并解码 Base64 编码的内容,支持一键复制解码结果
// @description:en  Automatically detects and decodes Base64 encoded text when selected, with one-click copy feature
// @author       EEP
// @license      MIT
// @match        *://*/*
// @grant        GM_addStyle
// @run-at       document-end
// @supportURL   https://github.com/eep/base64-decoder/issues
// @homepageURL  https://github.com/eep/base64-decoder
// ==/UserScript==

(function () {
  'use strict';

  let floatingWindow = null;
  let decodedText = '';

  // 创建悬浮窗口元素
  function createFloatingWindow() {
    if (floatingWindow) return floatingWindow;

    floatingWindow = document.createElement('div');
    floatingWindow.style.cssText = `
        position: absolute;
        padding: 10px;
        background: rgba(0, 0, 0, 0.8);
        color: white;
        border-radius: 5px;
        font-size: 14px;
        z-index: 2147483647;
        display: none;
        cursor: pointer;
        user-select: none;
        pointer-events: auto;
        max-width: 80%;
        word-wrap: break-word;
        box-shadow: 0 2px 5px rgba(0,0,0,0.2);
        left: 0;
        top: 0;
    `;
    floatingWindow.textContent = '';
    document.body.appendChild(floatingWindow);

    // 添加复制功能
    floatingWindow.addEventListener('click', async (e) => {
      e.stopPropagation();
      if (decodedText) {
        try {
          await navigator.clipboard.writeText(decodedText);
          const originalText = floatingWindow.textContent;
          floatingWindow.textContent = '复制成功!';
          setTimeout(() => {
            floatingWindow.textContent = originalText;
          }, 1000);
        } catch (err) {
          console.error('复制失败:', err);
        }
      }
    });

    return floatingWindow;
  }

  // 判断字符串是否为纯英文
  function isEnglishOnly(text) {
    // 先对文本进行trim处理
    text = text.trim();
    // 检查是否只包含合法的 base64 字符
    if (!/^[A-Za-z0-9+/=]+$/.test(text)) {
      return false;
    }
    // 如果包含空格,则认为是普通英文句子
    if (text.includes(' ')) {
      return false;
    }
    // 检查长度是否为 4 的倍数
    if (text.length % 4 !== 0) {
      return false;
    }
    // 检查填充字符的位置是否正确
    const paddingIndex = text.indexOf('=');
    if (paddingIndex !== -1) {
      // 确保等号只出现在末尾,且最多只有两个
      const paddingCount = text.length - paddingIndex;
      if (paddingCount > 2 || paddingIndex !== text.length - paddingCount) {
        return false;
      }
    }
    return true;
  }

  // 尝试base64解码
  function tryBase64Decode(text) {
    try {
      const decoded = atob(text.trim());
      // 检查解码后的文本是否包含过多不可打印字符
      const unprintableChars = decoded.split('').filter((char) => {
        const code = char.charCodeAt(0);
        return code < 32 || code > 126;
      }).length;

      // 如果不可打印字符超过总长度的 20%,认为不是有效的文本
      if (unprintableChars / decoded.length > 0.2) {
        return null;
      }
      return decoded;
    } catch (e) {}
    return null;
  }

  // 用于存储延时器ID
  let decodeTimer = null;

  // 处理选择事件
  document.addEventListener('selectionchange', () => {
    // 清除之前的延时器
    if (decodeTimer) {
      clearTimeout(decodeTimer);
    }

    const selection = window.getSelection();
    const text = selection.toString().trim(); // 先对选中文本进行trim处理

    // 如果没有选中文本或不符合解码条件,则隐藏窗口
    if (!text || !/^[A-Za-z0-9+/=]+$/.test(text) || !isEnglishOnly(text)) {
      if (floatingWindow) {
        floatingWindow.style.display = 'none';
      }
      return;
    }

    if (text && /^[A-Za-z0-9+/=]+$/.test(text) && isEnglishOnly(text)) {
      // 设置200ms延时
      decodeTimer = setTimeout(() => {
        const decoded = tryBase64Decode(text);
        if (decoded) {
          const range = selection.getRangeAt(0);
          const rect = range.getBoundingClientRect();

          decodedText = decoded;
          const window = createFloatingWindow();
          window.textContent = `Decoded: ${decoded}`;
          window.style.display = 'block';

          // 计算窗口位置,考虑页面滚动
          const scrollX =
            window.scrollX ||
            window.pageXOffset ||
            document.documentElement.scrollLeft;
          const scrollY =
            window.scrollY ||
            window.pageYOffset ||
            document.documentElement.scrollTop;

          // 使用选区位置信息
          let finalLeft = Math.round(rect.left + scrollX);
          let finalTop = Math.round(rect.bottom + scrollY + 5); // 在选中文本下方5px处

          // 获取浮动窗口的尺寸
          const windowWidth = window.offsetWidth;
          const windowHeight = window.offsetHeight;

          // 确保窗口不会超出视口右边界
          const maxRight = document.documentElement.clientWidth + scrollX - 10;
          if (finalLeft + windowWidth > maxRight) {
            finalLeft = maxRight - windowWidth;
          }
          // 确保不会超出左边界
          finalLeft = Math.max(scrollX + 10, finalLeft);

          // 确保窗口不会超出视口底部边界
          const maxBottom = window.innerHeight + scrollY - 10;
          if (finalTop + windowHeight > maxBottom) {
            // 如果下方空间不足,尝试显示在选中文本上方
            finalTop = rect.top + scrollY - windowHeight - 5;
            if (finalTop < scrollY + 10) {
              // 如果上方空间也不足,则调整窗口宽度并显示在下方
              finalTop = rect.bottom + scrollY + 5;
              window.style.maxWidth = '50%';
            }
          } else {
            window.style.maxWidth = '80%';
          }

          window.style.left = `${finalLeft}px`;
          window.style.top = `${finalTop}px`;
        }
      }, 200);
    }
  });

  // 点击页面其他地方时隐藏悬浮窗口
  document.addEventListener('mousedown', (e) => {
    if (
      floatingWindow &&
      e.target !== floatingWindow &&
      !floatingWindow.contains(e.target)
    ) {
      floatingWindow.style.display = 'none';
    }
  });

  // 防止选中文本时触发窗口隐藏
  floatingWindow &&
    floatingWindow.addEventListener('mousedown', (e) => {
      e.stopPropagation();
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址