Greasy Fork

Greasy Fork is available in English.

Websites Base64 Helper

Base64编解码工具 for all websites

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴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.14
// @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
// ==/UserScript==

(function () {
	'use strict';

	// 常量定义
	const Z_INDEX = 2147483647;
	const STORAGE_KEYS = {
		BUTTON_POSITION: 'btnPosition',
	};
	const BASE64_REGEX = /(?<!\w)([A-Za-z0-9+/]{6,}?={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);
                                position: relative;
                height: auto;
                max-height: 100px;
            }
            .base64-notification.fade-out {
                animation: slideUpOut 0.3s 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;
            }
            .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 {
		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.isRealDragging = false; // 添加真实拖动标记
			this.touchStartX = 0; // 添加触摸开始位置
			this.touchStartY = 0; // 添加触摸开始位置
			this.menuVisible = false;
			this.resizeTimer = null;
			this.notifications = [];
			this.notificationContainer = null;
			this.notificationEventListeners = []; // 添加通知事件监听器集合
			this.initUI();
			this.eventListeners = []; // 用于存储事件监听器以便后续清理
			this.initEventListeners();
			this.addRouteListeners();
		}

		// 添加正则常量
		static URL_PATTERNS = {
			// 更新 URL 检测正则,添加常见域名特征的判断
			URL: /^(?!.*(?:[a-z0-9-]+\.(?:com|net|org|edu|gov|mil|biz|info|io|cn|me|tv|cc|uk|jp|ru|eu|au|de|fr)(?:\/|\?|#|$)))(?:(?: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() {
			// 确保只在主文档中初始化UI
			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.addTouchListeners();
			this.addMouseListeners();

			// 其他通用事件监听
			const commonListeners = [
				{
					element: this.decodeBtn,
					events: [
						{ name: 'click', handler: (e) => { e.stopPropagation(); this.handleDecode(); } },
						{ name: 'touchstart', handler: (e) => { e.stopPropagation(); } }
					]
				},
				{
					element: this.encodeBtn,
					events: [
						{ name: 'click', handler: (e) => { e.stopPropagation(); this.handleEncode(); } },
						{ name: 'touchstart', handler: (e) => { e.stopPropagation(); } }
					]
				},
				{
					element: window,
					events: [{ name: 'resize', handler: () => this.handleResize() }]
				}
			];

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

			// 将全局点击事件的监听放在最后
			this.addGlobalClickListeners();
		}

		addGlobalClickListeners() {
			const handleOutsideClick = (e) => {
				// 确保点击不是来自菜单或按钮本身
				const helper = this.shadowRoot.querySelector('.base64-helper');
				if (helper && !helper.contains(e.target) && this.menuVisible) {
					this.menuVisible = false;
					this.menu.style.display = 'none';
				}
			};

			document.addEventListener('click', handleOutsideClick);
			document.addEventListener('touchstart', handleOutsideClick);

			this.eventListeners.push(
				{ element: document, event: 'click', handler: handleOutsideClick },
				{ element: document, event: 'touchstart', handler: handleOutsideClick }
			);
		}

		addTouchListeners() {
			const touchHandlers = [
				{
					event: 'touchstart',
					handler: (e) => this.handleTouchStart(e),
					options: { passive: false },
				},
				{
					event: 'touchmove',
					handler: (e) => this.handleTouchMove(e),
					options: { passive: false },
				},
				{ event: 'touchend', handler: (e) => this.handleTouchEnd(e) },
				{ event: 'click', handler: (e) => this.toggleMenu(e) },
			];

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

			// 更新全局点击事件监听
			const handleGlobalClick = (e) => {
				// 如果菜单没有显示,不需要处理
				if (!this.menuVisible) return;

				// 获取事件目标的组合路径
				const path = e.composedPath();

				// 检查点击路径中是否包含我们的 helper 容器
				const helperContainer = this.shadowRoot.querySelector('.base64-helper');
				if (!path.includes(helperContainer)) {
					this.menuVisible = false;
					this.menu.style.display = 'none';
				}
			};

			document.addEventListener('click', handleGlobalClick, true);
			document.addEventListener('touchstart', handleGlobalClick, true);

			this.eventListeners.push(
				{ element: document, event: 'click', handler: handleGlobalClick },
				{ element: document, event: 'touchstart', handler: handleGlobalClick }
			);
		}

		addMouseListeners() {
			const mouseHandlers = [
				{
					element: this.mainBtn,
					event: 'mousedown',
					handler: (e) => this.handleMouseDown(e),
				},
				{
					element: document,
					event: 'mousemove',
					handler: (e) => this.handleMouseMove(e),
				},
				{
					element: document,
					event: 'mouseup',
					handler: (e) => this.handleMouseUp(e),
				},
				{
					element: this.mainBtn,
					event: 'click',
					handler: (e) => this.toggleMenu(e),
				},
			];

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

		// 鼠标事件处理
		handleMouseDown(e) {
			if (e.button !== 0) return; // 只处理左键点击
			this.isMouseDragging = true;
			this.startX = e.clientX;
			this.startY = e.clientY;
			const rect = this.shadowRoot
				.querySelector('.base64-helper')
				.getBoundingClientRect();
			this.initialX = rect.left;
			this.initialY = rect.top;
			const ui = this.shadowRoot.querySelector('.base64-helper');
			const btn = ui.querySelector('.main-btn');
			ui.style.transition = 'none';
			ui.style.cursor = 'grabbing';
			btn.style.cursor = 'grabbing';
			this.mouseStartTime = Date.now();
		}

		handleMouseMove(e) {
			if (!this.isMouseDragging) return;

			const moveX = Math.abs(e.clientX - this.startX);
			const moveY = Math.abs(e.clientY - this.startY);

			if (moveX > 5 || moveY > 5) {
				this.hasMouseMoved = true;
			}

			const dx = e.clientX - this.startX;
			const dy = e.clientY - this.startY;

			const newX = Math.min(
				Math.max(20, this.initialX + dx),
				window.innerWidth - this.mainBtn.offsetWidth - 20
			);
			const newY = Math.min(
				Math.max(20, this.initialY + dy),
				window.innerHeight - this.mainBtn.offsetHeight - 20
			);

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

		handleMouseUp(e) {
			if (!this.isMouseDragging) return;

			const ui = this.shadowRoot.querySelector('.base64-helper');
			const btn = ui.querySelector('.main-btn');
			const currentRect = ui.getBoundingClientRect();

			this.isMouseDragging = false;
			const pos = this.positionManager.set(currentRect.left, currentRect.top);
			ui.style.left = `${pos.x}px`;
			ui.style.top = `${pos.y}px`;
			ui.style.transition = 'opacity 0.3s ease';
			ui.style.cursor = 'grab';
			btn.style.cursor = 'grab';

			// 如果没有移动,则触发菜单
			if (!this.hasMouseMoved) {
				this.toggleMenu({ stopPropagation: () => {} });
			}

			this.hasMouseMoved = false;
		}

		// 触屏事件处理
		handleTouchStart(e) {
			if (e.touches.length === 1) {
				e.preventDefault();
				this.isTouchDragging = true;
				const touch = e.touches[0];
				this.startX = touch.clientX;
				this.startY = touch.clientY;
				const rect = this.shadowRoot
					.querySelector('.base64-helper')
					.getBoundingClientRect();
				this.initialX = rect.left;
				this.initialY = rect.top;
				this.touchStartTime = Date.now();
				const ui = this.shadowRoot.querySelector('.base64-helper');
				ui.style.transition = 'none';
			}
		}

		handleTouchMove(e) {
			if (!this.isTouchDragging || e.touches.length !== 1) return;
			e.preventDefault();

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

			if (moveX > 5 || moveY > 5) {
				this.hasTouchMoved = true;
			}

			const dx = touch.clientX - this.startX;
			const dy = touch.clientY - this.startY;

			const newX = Math.min(
				Math.max(20, this.initialX + dx),
				window.innerWidth - this.mainBtn.offsetWidth - 20
			);
			const newY = Math.min(
				Math.max(20, this.initialY + dy),
				window.innerHeight - this.mainBtn.offsetHeight - 20
			);

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

		handleTouchEnd(e) {
			if (!this.isTouchDragging) return;

			const ui = this.shadowRoot.querySelector('.base64-helper');
			const currentRect = ui.getBoundingClientRect();
			const touchDuration = Date.now() - this.touchStartTime;

			this.isTouchDragging = false;
			const pos = this.positionManager.set(currentRect.left, currentRect.top);
			ui.style.left = `${pos.x}px`;
			ui.style.top = `${pos.y}px`;
			ui.style.transition = 'opacity 0.3s ease';

			// 如果触摸时间短且没有明显移动,则触发菜单
			if (touchDuration < 200 && !this.hasTouchMoved) {
				this.toggleMenu({ stopPropagation: () => {} });
			}

			this.hasTouchMoved = false;
		}

		toggleMenu(e) {
			e.stopPropagation();

			// 如果正在拖动,不处理点击
			if (this.isTouchDragging || this.isMouseDragging) return;

			// 获取当前位置,检查是否有移动
			const hasMoved = this.hasTouchMoved || this.hasMouseMoved;

			// 如果没有移动,则切换菜单
			if (!hasMoved) {
				this.menuVisible = !this.menuVisible;
				this.menu.style.display = this.menuVisible ? 'block' : 'none';
			}
		}

		// 窗口resize处理
		handleResize() {
			clearTimeout(this.resizeTimer);
			this.resizeTimer = setTimeout(() => {
				const pos = this.positionManager.get();
				if (pos) {
					const ui = this.shadowRoot.querySelector('.base64-helper');
					ui.style.left = `${pos.x}px`;
					ui.style.top = `${pos.y}px`;
				}
			}, 100);
		}

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

			const routeEvents = [
				{ event: 'popstate', target: window },
				{ event: 'hashchange', target: window },
				{ event: 'DOMContentLoaded', target: window },
			];

			routeEvents.forEach(({ event, target }) => {
				target.addEventListener(event, this.handleRouteChange);
				this.eventListeners.push({
					element: target,
					event,
					handler: this.handleRouteChange,
				});
			});

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

		// 核心功能
		handleDecode() {
			if (this.decodeBtn.dataset.mode === 'restore') {
				this.restoreContent();
				return;
			}

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

				if (validDecodedCount === 0) {
					this.showNotification('本页未发现有效 Base64 内容', 'info');
					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';
		}

		processTextNodes() {
			// 添加超时保护
			const startTime = Date.now();
			const TIMEOUT = 5000; // 5秒超时

			// 扩展排除的标签列表
			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',
			]);

			// 排除的属性名
			const excludeAttrs = new Set([
				'src',
				'data-src',
				'href',
				'data-url',
				'content',
				'background',
				'poster',
				'data-image',
				'srcset',
			]);

			const walker = document.createTreeWalker(
				document.body,
				NodeFilter.SHOW_TEXT,
				{
					acceptNode: (node) => {
						// Helper functions to check node validity
						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;
						};

						// Check parent chain
						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;
						}

						// Check text content
						const text = node.textContent?.trim();
						if (!text || text.length < 8) {
							return NodeFilter.FILTER_SKIP;
						}

						// Check for potential base64 content
						return /[A-Za-z0-9+/]{6,}/.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 };
		}

		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];
				if (!this.validateBase64(original)) continue;

				try {
					const decoded = this.decodeBase64(original);
					// 检查解码结果是否为有效文本
					if (!decoded || !this.isValidText(decoded)) continue;

					const matchId = `${original}-${match.index}`;
					if (processedMatches.has(matchId)) continue;

					processedMatches.add(matchId);
					newHtml = `${newHtml.substring(
						0,
						match.index
					)}<span class="decoded-text" title="点击复制" data-original="${original}">${decoded}</span>${newHtml.substring(
						match.index + original.length
					)}`;
					modified = true;
					count++;
				} catch (e) {
					continue;
				}
			}

			return { modified, newHtml, count };
		}

		// 添加新的辅助方法来验证解码后的文本
		isValidText(text) {
			// 检查解码后的文本是否包含可打印字符
			if (!text || text.length === 0) return false;

			// 检查是否包含过多的不可打印字符
			const printableChars = text.replace(/[^\x20-\x7E]/g, '').length;
			return printableChars / text.length > 0.5; // 可打印字符需占50%以上
		}

		replaceNodes(nodesToReplace) {
			// 检查是否包含过多的不可打印字符
			const printableChars = text.replace(/[^\x20-\x7E]/g, '').length;
			return printableChars / text.length > 0.5; // 可打印字符需占50%以上
		}

		replaceNodes(nodesToReplace) {
			nodesToReplace.forEach(({ node, newHtml }) => {
				const span = document.createElement('span');
				span.innerHTML = newHtml;
				node.parentNode.replaceChild(span, node);
			});
		}

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

		async handleEncode() {
			const text = prompt('请输入要编码的文本:');
			if (text === null) return;

			try {
				const encoded = this.encodeBase64(text);
				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';
		}

		// 工具方法
		validateBase64(str) {
			// 1. 快速预检查
			if (!str || str.length < 8 || str.length > 1000) return false;

			// 2. 检查是否包含常见的网址相关字符串
			const patterns = Base64Helper.URL_PATTERNS.DOMAIN_PATTERNS;

			// 快速检查是否包含常见网站特征
			if (
				patterns.POPULAR_SITES.test(str) ||
				patterns.VIDEO_SITES.test(str) ||
				patterns.CN_SITES.test(str) ||
				patterns.TLD.test(str)
			) {
				return false;
			}

			// 3. Base64 格式检查
			if (!str.match(/^[A-Za-z0-9+/]*={0,2}$/)) return false;

			// 4. Base64 规则验证
			if (str.length % 4 !== 0) return false;
			if (str.includes('==')) {
				if (!str.endsWith('==')) return false;
			} else if (str.includes('=')) {
				if (!str.endsWith('=')) return false;
			}

			// 5. 检查有效长度(去掉padding后)
			return str.replace(/=+$/, '').length >= 8;
		}

		decodeBase64(str) {
			return decodeURIComponent(
				atob(str)
					.split('')
					.map((c) => `%${c.charCodeAt(0).toString(16).padStart(2, '0')}`)
					.join('')
			);
		}

		encodeBase64(str) {
			return btoa(
				encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) =>
					String.fromCharCode(`0x${p1}`)
				)
			);
		}

		copyToClipboard(text) {
			// 尝试使用 navigator.clipboard API
			if (navigator.clipboard && window.isSecureContext) {
				return navigator.clipboard
					.writeText(text)
					.then(() => true)
					.catch(() => this.fallbackCopy(text));
			}

			return this.fallbackCopy(text);
		}

		fallbackCopy(text) {
			// 尝试使用 GM_setClipboard
			if (typeof GM_setClipboard !== 'undefined') {
				try {
					GM_setClipboard(text);
					return true;
				} catch (e) {
					console.debug('GM_setClipboard failed:', e);
				}
			}

			// 使用 execCommand 作为后备方案
			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)) {
					// iOS 设备特殊处理
					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;
			}
		}

		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';
		}

		resetState() {
			if (this.decodeBtn.dataset.mode === 'restore') {
				this.restoreContent();
			}
		}

		// 处理通知的动画
		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%)';
		}

		// 处理通知的淡出
		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%)';
				}
			});
		}

		// 处理通知容器的清理
		cleanupNotificationContainer() {
			this.notificationEventListeners.forEach(({ element, event, handler }) => {
				element.removeEventListener(event, handler);
			});
			this.notificationEventListeners = [];
			this.notificationContainer.remove();
			this.notificationContainer = null;
		}

		// 处理通知过渡结束
		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();
				}
			}
		}

		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);
		}
	}

	// 初始化逻辑修改
	if (window.__base64HelperInstance || window.top !== window.self) {
		return window.__base64HelperInstance;
	}

	// 只在主文档中初始化
	if (window.top === window.self) {
		initStyles();
		const instance = new Base64Helper();
		if (instance) {
			// 确保实例创建成功
			window.__base64HelperInstance = instance;
		}
	}

	// 页面卸载时清理
	window.addEventListener('unload', () => {
		if (window.__base64HelperInstance) {
			window.__base64HelperInstance.destroy();
			delete window.__base64HelperInstance;
		}
	});
})();