Base64 文本解码器

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
    });
})();