Greasy Fork

Greasy Fork is available in English.

Websites Base64 Helper

Base64编解码工具 for all websites

当前为 2025-04-08 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Websites Base64 Helper
// @icon         https://raw.githubusercontent.com/XavierBar/Discourse-Base64-Helper/refs/heads/main/discourse.svg
// @namespace    http://tampermonkey.net/
// @version      1.4.33
// @description  Base64编解码工具 for all websites
// @author       Xavier
// @match        *://*/*
// @grant        GM_notification
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-idle
// @noframes     true
// ==/UserScript==

(function () {
	('use strict');

	// 常量定义
	const Z_INDEX = 2147483647;
	const STORAGE_KEYS = {
		BUTTON_POSITION: 'btnPosition',
	};
	const BASE64_REGEX = /([A-Za-z0-9+/]+={0,2})(?!\w)/g;
	// 样式常量
	const STYLES = {
		GLOBAL: `
            /* 基础内容样式 */
            .decoded-text {
                cursor: pointer;
                transition: all 0.2s;
                padding: 1px 3px;
                border-radius: 3px;
                background-color: #fff3cd !important;
                color: #664d03 !important;
            }
            .decoded-text:hover {
                background-color: #ffe69c !important;
            }
            /* 通知动画 */
            @keyframes slideIn {
                from {
                    transform: translateY(-20px);
                    opacity: 0;
                }
                to {
                    transform: translateY(0);
                    opacity: 1;
                }
            }
            @keyframes fadeOut {
                from { opacity: 1; }
                to { opacity: 0; }
            }
            /* 暗色模式全局样式 */
            @media (prefers-color-scheme: dark) {
                .decoded-text {
                    background-color: #332100 !important;
                    color: #ffd54f !important;
                }
                .decoded-text:hover {
                    background-color: #664d03 !important;
                }
            }
        `,
		NOTIFICATION: `
            @keyframes slideUpOut {
                0% {
                    transform: translateY(0) scale(1);
                    opacity: 1;
                }
                100% {
                    transform: translateY(-30px) scale(0.95);
                    opacity: 0;
                }
            }
            .base64-notifications-container {
                position: fixed;
                top: 20px;
                left: 50%;
                transform: translateX(-50%);
                z-index: ${Z_INDEX};
                display: flex;
                flex-direction: column;
                gap: 0;
                pointer-events: none;
                align-items: center;
                width: fit-content;
            }
            .base64-notification {
                transform-origin: top center;
                white-space: nowrap;
                padding: 12px 24px;
                border-radius: 8px;
                margin-bottom: 10px;
                animation: slideIn 0.3s ease forwards;
                font-family: system-ui, -apple-system, sans-serif;
                backdrop-filter: blur(4px);
                border: 1px solid rgba(255, 255, 255, 0.1);
                text-align: center;
                line-height: 1.5;
                background: rgba(255, 255, 255, 0.95);
                color: #2d3748;
                box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
                opacity: 1;
                transform: translateY(0);
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                will-change: transform, opacity;
                position: relative;
                height: auto;
                max-height: 100px;
            }
            .base64-notification.fade-out {
                animation: slideUpOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
                margin-bottom: 0 !important;
                max-height: 0 !important;
                padding-top: 0 !important;
                padding-bottom: 0 !important;
                border-width: 0 !important;
            }
            .base64-notification[data-type="success"] {
                background: rgba(72, 187, 120, 0.95) !important;
                color: #f7fafc !important;
            }
            .base64-notification[data-type="error"] {
                background: rgba(245, 101, 101, 0.95) !important;
                color: #f8fafc !important;
            }
            .base64-notification[data-type="info"] {
                background: rgba(66, 153, 225, 0.95) !important;
                color: #f7fafc !important;
            }
            @media (prefers-color-scheme: dark) {
                .base64-notification {
                    background: rgba(26, 32, 44, 0.95) !important;
                    color: #e2e8f0 !important;
                    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
                    border-color: rgba(255, 255, 255, 0.05);
                }
                .base64-notification[data-type="success"] {
                    background: rgba(22, 101, 52, 0.95) !important;
                }
                .base64-notification[data-type="error"] {
                    background: rgba(155, 28, 28, 0.95) !important;
                }
                .base64-notification[data-type="info"] {
                    background: rgba(29, 78, 216, 0.95) !important;
                }
            }
        `,
		SHADOW_DOM: `
            :host {
                all: initial !important;
                position: fixed !important;
                z-index: ${Z_INDEX} !important;
                pointer-events: none !important;
            }
            .base64-helper {
                position: fixed;
                z-index: ${Z_INDEX} !important;
                transform: translateZ(100px);
                cursor: drag;
                font-family: system-ui, -apple-system, sans-serif;
                opacity: 0.5;
                transition: opacity 0.3s ease, transform 0.2s;
                pointer-events: auto !important;
                will-change: transform;
            }
            .base64-helper.dragging {
                cursor: grabbing;
            }
						.base64-helper:hover {
              opacity: 1 !important;
            }
            .main-btn {
                background: #ffffff;
                color: #000000 !important;
                padding: 8px 16px;
                border-radius: 6px;
                box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
                font-weight: 500;
                user-select: none;
                transition: all 0.2s;
                font-size: 14px;
                cursor: drag;
                border: none !important;
            }
            .main-btn.dragging {
                cursor: grabbing;
            }
            .menu {
                position: absolute;
                bottom: calc(100% + 5px);
                right: 0;
                background: #ffffff;
                border-radius: 6px;
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
                display: none;
                min-width: auto !important;
                width: max-content !important;
                overflow: hidden;
            }
            .menu-item {
                padding: 8px 12px !important;
                color: #333 !important;
                transition: all 0.2s;
                font-size: 13px;
                cursor: pointer;
                position: relative;
                border-radius: 0 !important;
                isolation: isolate;
                white-space: nowrap !important;
                // 新增以下样式防止文本被选中
                user-select: none;
                -webkit-user-select: none;
                -moz-user-select: none;
                -ms-user-select: none;
            }
            .menu-item:hover::before {
                content: '';
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: currentColor;
                opacity: 0.1;
                z-index: -1;
            }
            @media (prefers-color-scheme: dark) {
                .main-btn {
                    background: #2d2d2d;
                    color: #fff !important;
                    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
                }
                .menu {
                    background: #1a1a1a;
                    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
                }
                .menu-item {
                    color: #e0e0e0 !important;
                }
                .menu-item:hover::before {
                    opacity: 0.08;
                }
            }
        `,
	};

	// 样式初始化
	const initStyles = () => {
		GM_addStyle(STYLES.GLOBAL + STYLES.NOTIFICATION);
	};

	class Base64Helper {
		/**
		 * Base64 Helper 类的构造函数
		 * @description 初始化所有必要的状态和UI组件,仅在主窗口中创建实例
		 * @throws {Error} 当在非主窗口中实例化时抛出错误
		 */
		constructor() {
			// 确保只在主文档中创建实例
			if (window.top !== window.self) {
				throw new Error(
					'Base64Helper can only be instantiated in the main window'
				);
			}

			this.originalContents = new Map();
			this.isDragging = false;
			this.hasMoved = false;
			this.startX = 0;
			this.startY = 0;
			this.initialX = 0;
			this.initialY = 0;
			this.startTime = 0;
			this.menuVisible = false;
			this.resizeTimer = null;
			this.notifications = [];
			this.notificationContainer = null;
			this.notificationEventListeners = [];
			this.initUI();
			this.eventListeners = [];
			this.initEventListeners();
			this.addRouteListeners();

			// 添加缓存对象
			this.base64Cache = new Map();
			this.MAX_TEXT_LENGTH = 10000; // 最大文本长度限制
		}

		// 添加正则常量
		static URL_PATTERNS = {
			URL: /^(?:(?:https?|ftp):\/\/)?(?:(?:[\w-]+\.)+[a-z]{2,}|localhost)(?::\d+)?(?:\/[^\s]*)?$/i,
			EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
			DOMAIN_PATTERNS: {
				POPULAR_SITES:
					/(?:google|youtube|facebook|twitter|instagram|linkedin|github|gitlab|bitbucket|stackoverflow|reddit|discord|twitch|tiktok|snapchat|pinterest|netflix|amazon|microsoft|apple|adobe)/i,
				VIDEO_SITES:
					/(?:bilibili|youku|iqiyi|douyin|kuaishou|nicovideo|vimeo|dailymotion)/i,
				CN_SITES:
					/(?:baidu|weibo|zhihu|taobao|tmall|jd|qq|163|sina|sohu|csdn|aliyun|tencent)/i,
				TLD: /\.(?:com|net|org|edu|gov|mil|biz|info|io|cn|me|tv|cc|uk|jp|ru|eu|au|de|fr)(?:\/|\?|#|$)/i,
			},
		};

		// UI 初始化
		initUI() {
			if (
				window.top !== window.self ||
				document.getElementById('base64-helper-root')
			) {
				return;
			}

			this.container = document.createElement('div');
			this.container.id = 'base64-helper-root';
			document.body.append(this.container);

			this.shadowRoot = this.container.attachShadow({ mode: 'open' });
			this.shadowRoot.appendChild(this.createShadowStyles());
			this.shadowRoot.appendChild(this.createMainUI());
			this.initPosition();
		}

		createShadowStyles() {
			const style = document.createElement('style');
			style.textContent = STYLES.SHADOW_DOM;
			return style;
		}

		createMainUI() {
			const uiContainer = document.createElement('div');
			uiContainer.className = 'base64-helper';
			uiContainer.style.cursor = 'grab';

			this.mainBtn = this.createButton('Base64', 'main-btn');
			this.mainBtn.style.cursor = 'grab';
			this.menu = this.createMenu();

			uiContainer.append(this.mainBtn, this.menu);
			return uiContainer;
		}

		createButton(text, className) {
			const btn = document.createElement('button');
			btn.className = className;
			btn.textContent = text;
			return btn;
		}

		createMenu() {
			const menu = document.createElement('div');
			menu.className = 'menu';

			this.decodeBtn = this.createMenuItem('解析本页 Base64', 'decode');
			this.encodeBtn = this.createMenuItem('文本转 Base64');

			menu.append(this.decodeBtn, this.encodeBtn);
			return menu;
		}

		createMenuItem(text, mode) {
			const item = document.createElement('div');
			item.className = 'menu-item';
			item.textContent = text;
			if (mode) item.dataset.mode = mode;
			return item;
		}

		// 位置管理
		initPosition() {
			const pos = this.positionManager.get() || {
				x: window.innerWidth - 120,
				y: window.innerHeight - 80,
			};

			const ui = this.shadowRoot.querySelector('.base64-helper');
			ui.style.left = `${pos.x}px`;
			ui.style.top = `${pos.y}px`;
		}

		get positionManager() {
			return {
				get: () => {
					const saved = GM_getValue(STORAGE_KEYS.BUTTON_POSITION);
					if (!saved) return null;

					const ui = this.shadowRoot.querySelector('.base64-helper');
					const maxX = window.innerWidth - ui.offsetWidth - 20;
					const maxY = window.innerHeight - ui.offsetHeight - 20;

					return {
						x: Math.min(Math.max(saved.x, 20), maxX),
						y: Math.min(Math.max(saved.y, 20), maxY),
					};
				},
				set: (x, y) => {
					const ui = this.shadowRoot.querySelector('.base64-helper');
					const pos = {
						x: Math.max(
							20,
							Math.min(x, window.innerWidth - ui.offsetWidth - 20)
						),
						y: Math.max(
							20,
							Math.min(y, window.innerHeight - ui.offsetHeight - 20)
						),
					};

					GM_setValue(STORAGE_KEYS.BUTTON_POSITION, pos);
					return pos;
				},
			};
		}

		// 初始化事件监听器
		initEventListeners() {
			this.addUnifiedEventListeners();
			this.addGlobalClickListeners();

			// 核心编解码事件监听
			const commonListeners = [
				{
					element: this.decodeBtn,
					events: [
						{
							name: 'click',
							handler: (e) => {
								e.preventDefault();
								e.stopPropagation();
								this.handleDecode();
							},
						},
					],
				},
				{
					element: this.encodeBtn,
					events: [
						{
							name: 'click',
							handler: (e) => {
								e.preventDefault();
								e.stopPropagation();
								this.handleEncode();
							},
						},
					],
				},
			];

			commonListeners.forEach(({ element, events }) => {
				events.forEach(({ name, handler }) => {
					element.addEventListener(name, handler, { passive: false });
					this.eventListeners.push({ element, event: name, handler });
				});
			});
		}

		addUnifiedEventListeners() {
			const ui = this.shadowRoot.querySelector('.base64-helper');
			const btn = this.mainBtn;

			// 统一的开始事件处理
			const startHandler = (e) => {
				e.preventDefault();
				e.stopPropagation();
				const point = e.touches ? e.touches[0] : e;
				this.isDragging = true;
				this.hasMoved = false;
				this.startX = point.clientX;
				this.startY = point.clientY;
				const rect = ui.getBoundingClientRect();
				this.initialX = rect.left;
				this.initialY = rect.top;
				this.startTime = Date.now();
				ui.style.transition = 'none';
				ui.classList.add('dragging');
				btn.style.cursor = 'grabbing';
			};

			// 统一的移动事件处理
			const moveHandler = (e) => {
				if (!this.isDragging) return;
				e.preventDefault();
				e.stopPropagation();

				const point = e.touches ? e.touches[0] : e;
				const moveX = Math.abs(point.clientX - this.startX);
				const moveY = Math.abs(point.clientY - this.startY);

				if (moveX > 5 || moveY > 5) {
					this.hasMoved = true;
					const dx = point.clientX - this.startX;
					const dy = point.clientY - this.startY;
					const newX = Math.min(
						Math.max(20, this.initialX + dx),
						window.innerWidth - ui.offsetWidth - 20
					);
					const newY = Math.min(
						Math.max(20, this.initialY + dy),
						window.innerHeight - ui.offsetHeight - 20
					);
					ui.style.left = `${newX}px`;
					ui.style.top = `${newY}px`;
				}
			};

			// 统一的结束事件处理
			const endHandler = (e) => {
				if (!this.isDragging) return;
				e.preventDefault();
				e.stopPropagation();

				this.isDragging = false;
				ui.classList.remove('dragging');
				btn.style.cursor = 'grab';
				ui.style.transition = 'opacity 0.3s ease';

				const duration = Date.now() - this.startTime;
				if (duration < 200 && !this.hasMoved) {
					this.toggleMenu(e);
				} else if (this.hasMoved) {
					const rect = ui.getBoundingClientRect();
					const pos = this.positionManager.set(rect.left, rect.top);
					ui.style.left = `${pos.x}px`;
					ui.style.top = `${pos.y}px`;
				}
			};

			// 统一收集所有事件监听器
			const listeners = [
				{
					element: ui,
					event: 'touchstart',
					handler: startHandler,
					options: { passive: false },
				},
				{
					element: ui,
					event: 'touchmove',
					handler: moveHandler,
					options: { passive: false },
				},
				{
					element: ui,
					event: 'touchend',
					handler: endHandler,
					options: { passive: false },
				},
				{ element: ui, event: 'mousedown', handler: startHandler },
				{ element: document, event: 'mousemove', handler: moveHandler },
				{ element: document, event: 'mouseup', handler: endHandler },
				{
					element: this.menu,
					event: 'touchstart',
					handler: (e) => e.stopPropagation(),
					options: { passive: false },
				},
				{
					element: this.menu,
					event: 'mousedown',
					handler: (e) => e.stopPropagation(),
				},
				{
					element: window,
					event: 'resize',
					handler: () => this.handleResize(),
				},
			];

			// 注册事件并保存引用
			listeners.forEach(({ element, event, handler, options }) => {
				element.addEventListener(event, handler, options);
				this.eventListeners.push({ element, event, handler, options });
			});
		}

		toggleMenu(e) {
			e?.preventDefault();
			e?.stopPropagation();

			// 如果正在拖动或已移动,不处理菜单切换
			if (this.isDragging || this.hasMoved) return;

			this.menuVisible = !this.menuVisible;
			this.menu.style.display = this.menuVisible ? 'block' : 'none';

			// 重置状态
			this.hasMoved = false;
		}

		addGlobalClickListeners() {
			const handleOutsideClick = (e) => {
				const ui = this.shadowRoot.querySelector('.base64-helper');
				const path = e.composedPath();
				if (!path.includes(ui) && this.menuVisible) {
					this.menuVisible = false;
					this.menu.style.display = 'none';
				}
			};

			// 将全局点击事件添加到 eventListeners 数组
			const globalListeners = [
				{
					element: document,
					event: 'click',
					handler: handleOutsideClick,
					options: true,
				},
				{
					element: document,
					event: 'touchstart',
					handler: handleOutsideClick,
					options: { passive: false },
				},
			];

			globalListeners.forEach(({ element, event, handler, options }) => {
				element.addEventListener(event, handler, options);
				this.eventListeners.push({ element, event, handler, options });
			});
		}

		// 路由监听
		addRouteListeners() {
			this.handleRouteChange = () => {
				clearTimeout(this.routeTimer);
				this.routeTimer = setTimeout(() => this.resetState(), 100);
			};

			// 添加路由相关事件到 eventListeners 数组
			const routeListeners = [
				{ element: window, event: 'popstate', handler: this.handleRouteChange },
				{
					element: window,
					event: 'hashchange',
					handler: this.handleRouteChange,
				},
				{
					element: window,
					event: 'DOMContentLoaded',
					handler: this.handleRouteChange,
				},
			];

			routeListeners.forEach(({ element, event, handler }) => {
				element.addEventListener(event, handler);
				this.eventListeners.push({ element, event, handler });
			});

			// 修改 history 方法
			this.originalPushState = history.pushState;
			this.originalReplaceState = history.replaceState;
			history.pushState = (...args) => {
				this.originalPushState.apply(history, args);
				this.handleRouteChange();
			};
			history.replaceState = (...args) => {
				this.originalReplaceState.apply(history, args);
				this.handleRouteChange();
			};
		}

		/**
		 * 处理页面中的Base64解码操作
		 * @description 根据当前模式执行解码或恢复操作
		 * 如果当前模式是restore则恢复原始内容,否则查找并解码页面中的Base64内容
		 * @fires showNotification 显示操作结果通知
		 */
		handleDecode() {
			if (this.decodeBtn.dataset.mode === 'restore') {
				this.restoreContent();
				return;
			}

			try {
				const { nodesToReplace, validDecodedCount } = this.processTextNodes();

				if (validDecodedCount === 0) {
					this.showNotification('本页未发现有效 Base64 内容', 'info');
					this.menuVisible = false;
					this.menu.style.display = 'none';
					return;
				}

				this.replaceNodes(nodesToReplace);
				this.addClickListenersToDecodedText();

				this.decodeBtn.textContent = '恢复本页 Base64';
				this.decodeBtn.dataset.mode = 'restore';
				this.showNotification(
					`解析完成,共找到 ${validDecodedCount} 个 Base64 内容`,
					'success'
				);
			} catch (e) {
				console.error('Base64 decode error:', e);
				this.showNotification(`解析失败: ${e.message}`, 'error');
			}

			this.menuVisible = false;
			this.menu.style.display = 'none';
		}

		/**
		 * 处理文本节点中的Base64内容
		 * @description 遍历文档中的文本节点,查找并处理其中的Base64内容
		 * @returns {Object} 处理结果
		 * @property {Array} nodesToReplace - 需要替换的节点数组
		 * @property {number} validDecodedCount - 有效的Base64解码数量
		 */
		processTextNodes() {
			const startTime = Date.now();
			const TIMEOUT = 5000;

			const excludeTags = new Set([
				'script',
				'style',
				'noscript',
				'iframe',
				'img',
				'input',
				'textarea',
				'svg',
				'canvas',
				'template',
				'pre',
				'code',
				'button',
				'meta',
				'link',
				'head',
				'title',
				'select',
				'form',
				'object',
				'embed',
				'video',
				'audio',
				'source',
				'track',
				'map',
				'area',
				'math',
				'figure',
				'picture',
				'portal',
				'slot',
				'data',
				'a',
				'base',           // 包含href属性的base标签
				'param',          // object的参数
				'applet',         // 旧版Java小程序
				'frame',          // 框架
				'frameset',       // 框架集
				'marquee',        // 滚动文本
				'time',           // 时间标签
				'wbr',            // 可能的换行符
				'bdo',            // 文字方向
				'dialog',         // 对话框
				'details',        // 详情
				'summary',        // 摘要
				'menu',           // 菜单
				'menuitem',       // 菜单项
				'[hidden]',       // 隐藏元素
				'[aria-hidden="true"]', // 可访问性隐藏
				'.base64',        // 自定义class
				'.encoded'        // 自定义class
			]);

			const excludeAttrs = new Set([
				'src',
				'data-src',
				'href',
				'data-url',
				'content',
				'background',
				'poster',
				'data-image',
				'srcset',
				'data-background',     // 背景图片
				'data-thumbnail',      // 缩略图
				'data-original',       // 原始图片
				'data-lazy',          // 懒加载
				'data-defer',         // 延迟加载
				'data-fallback',      // 后备图片
				'data-preview',       // 预览图
				'data-avatar',        // 头像
				'data-icon',          // 图标
				'data-base64',        // 显式标记的base64
				'style',              // 内联样式可能包含base64
				'integrity',          // SRI完整性校验
				'crossorigin',        // 跨域属性
				'rel',                // 关系属性
				'alt',                // 替代文本
				'title'              // 标题属性
			]);

			const walker = document.createTreeWalker(
				document.body,
				NodeFilter.SHOW_TEXT,
				{
					acceptNode: (node) => {
						const isExcludedTag = (parent) => {
							const tagName = parent.tagName?.toLowerCase();
							return excludeTags.has(tagName);
						};

						const isHiddenElement = (parent) => {
							if (!(parent instanceof HTMLElement)) return false;
							const style = window.getComputedStyle(parent);
							return (
								style.display === 'none' ||
								style.visibility === 'hidden' ||
								style.opacity === '0' ||
								style.clipPath === 'inset(100%)' ||
								(style.height === '0px' && style.overflow === 'hidden')
							);
						};

						const isOutOfViewport = (parent) => {
							if (!(parent instanceof HTMLElement)) return false;
							const rect = parent.getBoundingClientRect();
							return rect.width === 0 || rect.height === 0;
						};

						const hasBase64Attributes = (parent) => {
							if (!parent.hasAttributes()) return false;
							for (const attr of parent.attributes) {
								if (excludeAttrs.has(attr.name)) {
									const value = attr.value.toLowerCase();
									if (
										value.includes('base64') ||
										value.match(/^[a-z0-9+/=]+$/i)
									) {
										return true;
									}
								}
							}
							return false;
						};

						let parent = node.parentNode;
						while (parent && parent !== document.body) {
							if (
								isExcludedTag(parent) ||
								isHiddenElement(parent) ||
								isOutOfViewport(parent) ||
								hasBase64Attributes(parent)
							) {
								return NodeFilter.FILTER_REJECT;
							}
							parent = parent.parentNode;
						}

						const text = node.textContent?.trim();
						if (!text) {
							return NodeFilter.FILTER_SKIP;
						}

						return /[A-Za-z0-9+/]+/.exec(text)
							? NodeFilter.FILTER_ACCEPT
							: NodeFilter.FILTER_SKIP;
					},
				},
				false
			);

			let nodesToReplace = [];
			let processedMatches = new Set();
			let validDecodedCount = 0;

			while (walker.nextNode()) {
				if (Date.now() - startTime > TIMEOUT) {
					console.warn('Base64 processing timeout');
					break;
				}

				const node = walker.currentNode;
				const { modified, newHtml, count } = this.processMatches(
					node.nodeValue,
					processedMatches
				);
				if (modified) {
					nodesToReplace.push({ node, newHtml });
					validDecodedCount += count;
				}
			}

			return { nodesToReplace, validDecodedCount };
		}

		/**
		 * 处理文本中的Base64匹配项
		 * @description 查找并处理文本中的Base64编码内容
		 * @param {string} text - 要处理的文本内容
		 * @param {Set} processedMatches - 已处理过的匹配项集合
		 * @returns {Object} 处理结果
		 * @property {boolean} modified - 文本是否被修改
		 * @property {string} newHtml - 处理后的HTML内容
		 * @property {number} count - 处理的Base64数量
		 */
		processMatches(text, processedMatches) {
			const matches = Array.from(text.matchAll(BASE64_REGEX));
			if (!matches.length) return { modified: false, newHtml: text, count: 0 };

			let modified = false;
			let newHtml = text;
			let count = 0;

			for (const match of matches.reverse()) {
				const original = match[0];

				// 使用 validateBase64 进行验证
				if (!this.validateBase64(original)) {
					console.log('Skipped: invalid Base64 string');
					continue;
				}

				try {
					const decoded = this.decodeBase64(original);
					console.log('Decoded:', decoded);

					if (!decoded) {
						console.log('Skipped: decode failed');
						continue;
					}

					
					// 将原始Base64和位置信息添加到已处理集合中,防止重复处理
					const matchKey = `${original}-${match.index}`;
					processedMatches.add(matchKey);
					
					// 构建新的HTML内容:
					// 1. 保留匹配位置之前的内容
					const beforeMatch = newHtml.substring(0, match.index);
					// 2. 插入解码后的内容,包装在span标签中
					const decodedSpan = `<span class="decoded-text" 
						title="点击复制" 
						data-original="${original}">${decoded}</span>`;
					// 3. 保留匹配位置之后的内容
					const afterMatch = newHtml.substring(match.index + original.length);
					
					// 组合新的HTML
					newHtml = beforeMatch + decodedSpan + afterMatch;
					
					// 标记内容已被修改
					modified = true;
					// 增加成功解码计数
					count++;
					
					// 记录日志
					console.log('成功解码: 发现有意义的文本或中文字符');
				} catch (e) {
					console.error('Error processing:', e);
					continue;
				}
			}

			return { modified, newHtml, count };
		}

		/**
		 * 判断文本是否有意义
		 * @description 通过一系列规则判断解码后的文本是否具有实际意义
		 * @param {string} text - 要验证的文本
		 * @returns {boolean} 如果文本有意义返回true,否则返回false
		 */
		isMeaningfulText(text) {
			// 1. 基本字符检查
			if (!text || typeof text !== 'string') return false;

			// 2. 长度检查
			if (text.length < 2 || text.length > 10000) return false;

			// 3. 文本质量检查
			const stats = {
				printable: 0,        // 可打印字符
				control: 0,          // 控制字符
				chinese: 0,          // 中文字符
				letters: 0,          // 英文字母
				numbers: 0,          // 数字
				punctuation: 0,      // 标点符号
				spaces: 0,           // 空格
				other: 0            // 其他字符
			};

			// 统计字符分布
			for (let i = 0; i < text.length; i++) {
				const char = text.charAt(i);
				const code = text.charCodeAt(i);

				if (/[\u4E00-\u9FFF]/.test(char)) {
					stats.chinese++;
					stats.printable++;
				} else if (/[a-zA-Z]/.test(char)) {
					stats.letters++;
					stats.printable++;
				} else if (/[0-9]/.test(char)) {
					stats.numbers++;
					stats.printable++;
				} else if (/[\s]/.test(char)) {
					stats.spaces++;
					stats.printable++;
				} else if (/[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(char)) {
					stats.punctuation++;
					stats.printable++;
				} else if (code < 32 || code === 127) {
					stats.control++;
				} else {
					stats.other++;
				}
			}

			// 4. 质量评估规则
			const totalChars = text.length;
			const printableRatio = stats.printable / totalChars;
			const controlRatio = stats.control / totalChars;
			const meaningfulRatio = (stats.chinese + stats.letters + stats.numbers) / totalChars;

			// 判断条件:
			// 1. 可打印字符比例必须大于90%
			// 2. 控制字符比例必须小于5%
			// 3. 有意义字符(中文、英文、数字)比例必须大于30%
			// 4. 空格比例不能过高(小于50%)
			// 5. 其他字符比例必须很低(小于10%)
			return (
				printableRatio > 0.9 &&
				controlRatio < 0.05 &&
				meaningfulRatio > 0.3 &&
				(stats.spaces / totalChars) < 0.5 &&
				(stats.other / totalChars) < 0.1
			);
		}

		/**
		 * 替换页面中的节点
		 * @description 使用新的HTML内容替换原有节点
		 * @param {Array} nodesToReplace - 需要替换的节点数组
		 * @param {Node} nodesToReplace[].node - 原始节点
		 * @param {string} nodesToReplace[].newHtml - 新的HTML内容
		 */
		replaceNodes(nodesToReplace) {
			nodesToReplace.forEach(({ node, newHtml }) => {
				const span = document.createElement('span');
				span.innerHTML = newHtml;
				node.parentNode.replaceChild(span, node);
			});
		}

		/**
		 * 为解码后的文本添加点击复制功能
		 * @description 为所有解码后的文本元素添加点击事件监听器
		 * @fires copyToClipboard 点击时触发复制操作
		 * @fires showNotification 显示复制结果通知
		 */
		addClickListenersToDecodedText() {
			document.querySelectorAll('.decoded-text').forEach((el) => {
				el.addEventListener('click', async (e) => {
					const success = await this.copyToClipboard(e.target.textContent);
					this.showNotification(
						success ? '已复制文本内容' : '复制失败,请手动复制',
						success ? 'success' : 'error'
					);
					e.stopPropagation();
				});
			});
		}

		/**
		 * 处理文本编码为Base64
		 * @description 提示用户输入文本并转换为Base64格式
		 * @async
		 * @fires showNotification 显示编码结果通知
		 * @fires copyToClipboard 复制编码结果到剪贴板
		 */
		async handleEncode() {
			const text = prompt('请输入要编码的文本:');
			if (text === null) return; // 用户点击取消

			// 添加空输入检查
			if (!text.trim()) {
				this.showNotification('请输入有效的文本内容', 'error');
				return;
			}

			try {
				// 处理输入文本:去除首尾空格和多余的换行符
				const processedText = text.trim().replace(/[\r\n]+/g, '\n');
				const encoded = this.encodeBase64(processedText);
				const success = await this.copyToClipboard(encoded);
				this.showNotification(
					success
						? 'Base64 已复制'
						: '编码成功但复制失败,请手动复制:' + encoded,
					success ? 'success' : 'info'
				);
			} catch (e) {
				this.showNotification('编码失败: ' + e.message, 'error');
			}
			this.menu.style.display = 'none';
		}

		/**
		 * 验证Base64字符串
		 * @description 检查字符串是否为有效的Base64格式
		 * @param {string} str - 要验证的字符串
		 * @returns {boolean} 如果是有效的Base64返回true,否则返回false
		 * @example
		 * validateBase64('SGVsbG8gV29ybGQ=') // returns true
		 * validateBase64('Invalid-Base64') // returns false
		 */
		validateBase64(str) {
			if (!str) return false;

			// 使用缓存避免重复验证
			if (this.base64Cache.has(str)) {
				return this.base64Cache.get(str);
			}

			// 1. 基本格式检查
			// - 长度必须是4的倍数
			// - 只允许包含合法的Base64字符
			// - =号只能出现在末尾,且最多2个
			if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(str)) {
				this.base64Cache.set(str, false);
				return false;
			}

			// 2. 长度检查
			// 过滤掉太短的字符串(至少8个字符)和过长的字符串(最多10000个字符)
			if (str.length < 8 || str.length > 10000) {
				this.base64Cache.set(str, false);
				return false;
			}

			// 3. 特征检查
			// 过滤掉可能是图片、视频等二进制数据的Base64
			if (/^(?:data:|iVBOR|R0lGO|\/9j\/4|PD94bW|JVBER)/.test(str)) {
				this.base64Cache.set(str, false);
				return false;
			}

			// 添加到 validateBase64 方法中
			const commonPatterns = {
				// 常见的二进制数据头部特征
				binaryHeaders: /^(?:data:|iVBOR|R0lGO|\/9j\/4|PD94bW|JVBER|UEsDB|H4sIA|77u\/|0M8R4)/,
				
				// 常见的文件类型标识
				fileSignatures: /^(?:UEs|PK|%PDF|GIF8|RIFF|OggS|ID3|ÿØÿ|8BPS)/,
				
				// 常见的编码标识
				encodingMarkers: /^(?:utf-8|utf-16|base64|quoted-printable|7bit|8bit|binary)/i,
				
				// 可疑的URL模式
				urlPatterns: /^(?:https?:|ftp:|data:|blob:|file:|ws:|wss:)/i,
				
				// 常见的压缩文件头部
				compressedHeaders: /^(?:eJw|H4s|Qk1Q|UEsD|N3q8|KLUv)/
			};

			// 在验证时使用这些模式
			if (commonPatterns.binaryHeaders.test(str) ||
				commonPatterns.fileSignatures.test(str) ||
				commonPatterns.encodingMarkers.test(str) ||
				commonPatterns.urlPatterns.test(str) ||
				commonPatterns.compressedHeaders.test(str)) {
				this.base64Cache.set(str, false);
				return false;
			}

			try {
				const decoded = this.decodeBase64(str);
				if (!decoded) {
					this.base64Cache.set(str, false);
					return false;
				}

				// 4. 解码后的文本验证
				// 检查解码后的文本是否有意义
				if (!this.isMeaningfulText(decoded)) {
					this.base64Cache.set(str, false);
					return false;
				}

				this.base64Cache.set(str, true);
				return true;
			} catch (e) {
				console.error('Base64 validation error:', e);
				this.base64Cache.set(str, false);
				return false;
			}
		}

		/**
		 * Base64解码
		 * @description 将Base64字符串解码为普通文本
		 * @param {string} str - 要解码的Base64字符串
		 * @returns {string|null} 解码后的文本,解码失败时返回null
		 * @example
		 * decodeBase64('SGVsbG8gV29ybGQ=') // returns 'Hello World'
		 */
		decodeBase64(str) {
			try {
				// 优化解码过程
				const binaryStr = atob(str);
				const bytes = new Uint8Array(binaryStr.length);
				for (let i = 0; i < binaryStr.length; i++) {
					bytes[i] = binaryStr.charCodeAt(i);
				}
				return new TextDecoder().decode(bytes);
			} catch (e) {
				console.error('Base64 decode error:', e);
				return null;
			}
		}

		/**
		 * Base64编码
		 * @description 将普通文本编码为Base64格式
		 * @param {string} str - 要编码的文本
		 * @returns {string|null} Base64编码后的字符串,编码失败时返回null
		 * @example
		 * encodeBase64('Hello World') // returns 'SGVsbG8gV29ybGQ='
		 */
		encodeBase64(str) {
			try {
				// 优化编码过程
				const bytes = new TextEncoder().encode(str);
				let binaryStr = '';
				for (let i = 0; i < bytes.length; i++) {
					binaryStr += String.fromCharCode(bytes[i]);
				}
				return btoa(binaryStr);
			} catch (e) {
				console.error('Base64 encode error:', e);
				return null;
			}
		}

		/**
		 * 复制文本到剪贴板
		 * @description 尝试使用现代API或降级方案将文本复制到剪贴板
		 * @param {string} text - 要复制的文本
		 * @returns {Promise<boolean>} 复制是否成功
		 * @example
		 * await copyToClipboard('Hello World') // returns true
		 */
		copyToClipboard(text) {
			if (navigator.clipboard && window.isSecureContext) {
				return navigator.clipboard
					.writeText(text)
					.then(() => true)
					.catch(() => this.fallbackCopy(text));
			}

			return this.fallbackCopy(text);
		}

		/**
		 * 降级复制方案
		 * @description 当现代复制API不可用时的备选复制方案
		 * @param {string} text - 要复制的文本
		 * @returns {boolean} 复制是否成功
		 * @private
		 */
		fallbackCopy(text) {
			if (typeof GM_setClipboard !== 'undefined') {
				try {
					GM_setClipboard(text);
					return true;
				} catch (e) {
					console.debug('GM_setClipboard failed:', e);
				}
			}

			try {
				const textarea = document.createElement('textarea');
				textarea.value = text;
				textarea.style.cssText = 'position:fixed;opacity:0;';
				document.body.appendChild(textarea);

				if (navigator.userAgent.match(/ipad|iphone/i)) {
					textarea.contentEditable = true;
					textarea.readOnly = false;

					const range = document.createRange();
					range.selectNodeContents(textarea);

					const selection = window.getSelection();
					selection.removeAllRanges();
					selection.addRange(range);
					textarea.setSelectionRange(0, 999999);
				} else {
					textarea.select();
				}

				const success = document.execCommand('copy');
				document.body.removeChild(textarea);
				return success;
			} catch (e) {
				console.debug('execCommand copy failed:', e);
				return false;
			}
		}

		/**
		 * 恢复原始内容
		 * @description 将所有解码后的内容恢复为原始的Base64格式
		 * @fires showNotification 显示恢复结果通知
		 */
		restoreContent() {
			document.querySelectorAll('.decoded-text').forEach((el) => {
				const textNode = document.createTextNode(el.dataset.original);
				el.parentNode.replaceChild(textNode, el);
			});
			this.originalContents.clear();
			this.decodeBtn.textContent = '解析本页 Base64';
			this.decodeBtn.dataset.mode = 'decode';
			this.showNotification('已恢复原始内容', 'success');
			this.menu.style.display = 'none';
		}

		/**
		 * 重置插件状态
		 * @description 重置所有状态变量并在必要时恢复原始内容
		 * @fires restoreContent 如果当前处于restore模式则触发内容恢复
		 */
		resetState() {
			if (this.decodeBtn.dataset.mode === 'restore') {
				this.restoreContent();
			}
		}

		/**
		 * 为通知添加动画效果
		 * @param {HTMLElement} notification - 通知元素
		 * @param {number} index - 通知索引
		 */
		animateNotification(notification, index) {
			const currentTransform = getComputedStyle(notification).transform;
			notification.style.transform = currentTransform;
			notification.style.transition = 'all 0.3s ease-out';
			notification.style.transform = 'translateY(-100%)';
		}

		/**
		 * 处理通知淡出效果
		 * @description 为通知添加淡出效果并处理相关动画
		 * @param {HTMLElement} notification - 要处理的通知元素
		 * @fires animateNotification 触发其他通知的位置调整动画
		 */
		handleNotificationFadeOut(notification) {
			notification.classList.add('fade-out');
			const index = this.notifications.indexOf(notification);

			this.notifications.slice(0, index).forEach((prev) => {
				if (prev.parentNode) {
					prev.style.transform = 'translateY(-100%)';
				}
			});
		}

		/**
		 * 清理通知容器
		 * @description 移除所有通知元素和相关事件监听器
		 * @fires removeEventListener 移除所有通知相关的事件监听器
		 */
		cleanupNotificationContainer() {
			// 清理通知相关的事件监听器
			this.notificationEventListeners.forEach(({ element, event, handler }) => {
				element.removeEventListener(event, handler);
			});
			this.notificationEventListeners = [];

			// 移除所有通知元素
			while (this.notificationContainer.firstChild) {
				this.notificationContainer.firstChild.remove();
			}

			this.notificationContainer.remove();
			this.notificationContainer = null;
		}

		/**
		 * 处理通知过渡结束事件
		 * @description 处理通知元素的过渡动画结束后的清理工作
		 * @param {TransitionEvent} e - 过渡事件对象
		 * @fires animateNotification 触发其他通知的位置调整
		 */
		handleNotificationTransitionEnd(e) {
			if (
				e.propertyName === 'opacity' &&
				e.target.classList.contains('fade-out')
			) {
				const notification = e.target;
				const index = this.notifications.indexOf(notification);

				this.notifications.forEach((notif, i) => {
					if (i > index && notif.parentNode) {
						this.animateNotification(notif, i);
					}
				});

				if (index > -1) {
					this.notifications.splice(index, 1);
					notification.remove();
				}

				if (this.notifications.length === 0) {
					this.cleanupNotificationContainer();
				}
			}
		}

		/**
		 * 显示通知消息
		 * @description 创建并显示一个通知消息,包含自动消失功能
		 * @param {string} text - 通知文本内容
		 * @param {string} type - 通知类型 ('success'|'error'|'info')
		 * @fires handleNotificationFadeOut 触发通知淡出效果
		 * @example
		 * showNotification('操作成功', 'success')
		 */
		showNotification(text, type) {
			if (!this.notificationContainer) {
				this.notificationContainer = document.createElement('div');
				this.notificationContainer.className = 'base64-notifications-container';
				document.body.appendChild(this.notificationContainer);

				const handler = (e) => this.handleNotificationTransitionEnd(e);
				this.notificationContainer.addEventListener('transitionend', handler);
				this.notificationEventListeners.push({
					element: this.notificationContainer,
					event: 'transitionend',
					handler,
				});
			}

			const notification = document.createElement('div');
			notification.className = 'base64-notification';
			notification.setAttribute('data-type', type);
			notification.textContent = text;

			this.notifications.push(notification);
			this.notificationContainer.appendChild(notification);

			setTimeout(() => {
				if (notification.parentNode) {
					this.handleNotificationFadeOut(notification);
				}
			}, 2000);
		}

		/**
		 * 销毁插件实例
		 * @description 清理所有资源,移除事件监听器,恢复原始状态
		 * @fires restoreContent 如果需要则恢复原始内容
		 * @fires removeEventListener 移除所有事件监听器
		 */
		destroy() {
			// 清理所有事件监听器
			this.eventListeners.forEach(({ element, event, handler, options }) => {
				element.removeEventListener(event, handler, options);
			});
			this.eventListeners = [];

			// 清理定时器
			if (this.resizeTimer) clearTimeout(this.resizeTimer);
			if (this.routeTimer) clearTimeout(this.routeTimer);

			// 清理通知相关资源
			if (this.notificationContainer) {
				this.cleanupNotificationContainer();
			}
			this.notifications = [];

			// 恢复原始的 history 方法
			if (this.originalPushState) history.pushState = this.originalPushState;
			if (this.originalReplaceState)
				history.replaceState = this.originalReplaceState;

			// 恢复原始状态
			if (this.decodeBtn?.dataset.mode === 'restore') {
				this.restoreContent();
			}

			// 移除 DOM 元素
			if (this.container) {
				this.container.remove();
			}

			// 清理引用
			this.shadowRoot = null;
			this.mainBtn = null;
			this.menu = null;
			this.decodeBtn = null;
			this.encodeBtn = null;
			this.container = null;
			this.originalContents.clear();
			this.originalContents = null;
			this.isDragging = false;
			this.hasMoved = false;
			this.menuVisible = false;
		}
	}

	// 确保只初始化一次
	if (window.__base64HelperInstance) {
		return;
	}

	// 只在主窗口中初始化
	if (window.top === window.self) {
		initStyles();
		window.__base64HelperInstance = new Base64Helper();
	}

	// 使用 { once: true } 确保事件监听器只添加一次
	window.addEventListener(
		'unload',
		() => {
			if (window.__base64HelperInstance) {
				window.__base64HelperInstance.destroy();
				delete window.__base64HelperInstance;
			}
		},
		{ once: true }
	);
})();