123pan JSON秒传链接工具

1.1.5:修复悬浮窗

当前为 2025-07-14 提交的版本,查看 最新版本

// ==UserScript==
// @name         123pan JSON秒传链接工具
// @namespace    http://tampermonkey.net/
// @version      1.1.5
// @description  1.1.5:修复悬浮窗
// @author       Tocpomk
// @match        *://*.www.123pan.com
// @match        *://www.123pan.cn
// @match        *://*.123865.com
// @match        *://*.123684.com
// @match        *://*.123912.com
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 添加样式
    GM_addStyle(`
        #json-tool-float-btn {
            position: fixed;
            top: 50%;
            right: 20px;
            transform: translateY(-50%);
            width: 60px;
            height: 60px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 50%;
            color: white;
            font-size: 24px;
            cursor: pointer;
            box-shadow: 0 4px 15px rgba(0,0,0,0.2);
            z-index: 9999;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            user-select: none;
            animation: pulse 2s infinite;
            touch-action: none;
        }

        #json-tool-float-btn:hover {
            transform: translateY(-50%) scale(1.1);
            box-shadow: 0 6px 20px rgba(0,0,0,0.3);
        }

        #json-tool-float-btn:active {
            transform: translateY(-50%) scale(0.95);
        }

        #json-tool-float-btn.dragging {
            opacity: 0.8;
            cursor: grabbing;
            transform: none !important;
            box-shadow: 0 8px 25px rgba(0,0,0,0.4);
        }

        #json-tool-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.5);
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0;
            visibility: hidden;
            transition: all 0.4s cubic-bezier(0.32, 0.72, 0, 1);
            pointer-events: none;
        }

        #json-tool-modal.show {
            opacity: 1;
            visibility: visible;
            pointer-events: all;
        }

        #json-tool-content {
            width: 90%;
            height: 90%;
            max-width: 1200px;
            background: white;
            border-radius: 16px;
            box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);
            display: flex;
            flex-direction: column;
            overflow: hidden;
            transform: translateY(50px) scale(0.95);
            opacity: 0;
            transition: all 0.5s cubic-bezier(0.32, 0.72, 0, 1);
        }

        #json-tool-modal.show #json-tool-content {
            transform: translateY(0) scale(1);
            opacity: 1;
        }

        #json-tool-close {
            position: absolute;
            top: 18px;
            right: 18px;
            background: rgba(0,0,0,0.2);
            border: none;
            color: white;
            font-size: 26px;
            cursor: pointer;
            width: 42px;
            height: 42px;
            border-radius: 50%;
            transition: all 0.3s ease;
            z-index: 10001;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        #json-tool-close:hover {
            background: rgba(0,0,0,0.5);
            transform: scale(1.15) rotate(90deg);
        }

        #json-tool-iframe {
            width: 100%;
            height: 100%;
            border: none;
            border-radius: 0 0 16px 16px;
        }

        #json-tool-header {
            height: 60px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0 24px;
            color: white;
            font-weight: bold;
            font-size: 18px;
            position: relative;
        }

        #json-tool-title {
            display: flex;
            align-items: center;
            gap: 12px;
        }

        #json-tool-title svg {
            width: 24px;
            height: 24px;
            fill: white;
        }

        .json-tool-drag-handle {
            width: 100%;
            height: 30px;
            position: absolute;
            top: 0;
            left: 0;
            cursor: move;
            z-index: 1;
        }

        /* 响应式设计 */
        @media (max-width: 768px) {
            #json-tool-content {
                width: 95%;
                height: 95%;
            }

            #json-tool-float-btn {
                width: 50px;
                height: 50px;
                font-size: 20px;
                right: 15px;
            }

            #json-tool-header {
                padding: 0 15px;
                height: 50px;
                font-size: 16px;
            }
        }

        @keyframes pulse {
            0% { box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.6); }
            70% { box-shadow: 0 0 0 12px rgba(102, 126, 234, 0); }
            100% { box-shadow: 0 0 0 0 rgba(102, 126, 234, 0); }
        }

        .json-tool-tooltip {
            position: absolute;
            top: -45px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0,0,0,0.7);
            color: white;
            padding: 8px 12px;
            border-radius: 6px;
            font-size: 14px;
            white-space: nowrap;
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        .json-tool-tooltip::after {
            content: '';
            position: absolute;
            top: 100%;
            left: 50%;
            transform: translateX(-50%);
            border-width: 6px;
            border-style: solid;
            border-color: rgba(0,0,0,0.7) transparent transparent transparent;
        }

        #json-tool-float-btn:hover .json-tool-tooltip {
            opacity: 1;
        }
        #json-tool-header-btns {
            display: flex;
            align-items: center;
            gap: 8px; /* 减小间距 */
            position: absolute;
            top: 50%; /* 垂直居中 */
            right: 18px;
            transform: translateY(-50%); /* 确保精确垂直居中 */
            z-index: 10001;
        }

        #json-tool-maximize, 
        #json-tool-close {
            background: rgba(0,0,0,0.2);
            border: none;
            color: white;
            width: 36px;
            height: 36px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.3s;
            padding: 0;
            margin: 0;
        }

        #json-tool-maximize:hover, 
        #json-tool-close:hover {
            background: rgba(0,0,0,0.4);
            transform: scale(1.1);
        }

        #json-tool-maximize svg, 
        #json-tool-close svg {
            width: 22px;
            height: 22px;
            display: block;
            margin: auto;
        }
    `);

    // 创建悬浮按钮
    function createFloatButton() {
        const btn = document.createElement('button');
        btn.id = 'json-tool-float-btn';
        btn.innerHTML = `
            <img src="https://ims.musejie.top/za/resource%20manager.svg" style="width:32px;height:32px;pointer-events:none;" />
            <div class="json-tool-tooltip">拖拽移动 点击打开</div>
        `;
        btn.title = 'JSON秒传链接工具';

        // 从存储中获取位置
        const savedPosition = GM_getValue('floatButtonPosition', null);
        if (savedPosition) {
            btn.style.left = `${savedPosition.x}px`;
            btn.style.top = `${savedPosition.y}px`;
            btn.style.right = 'auto';
            btn.style.transform = 'none';
        } else {
            // 默认右侧20px 垂直居中
            btn.style.right = '20px';
            btn.style.top = '50%';
            btn.style.left = 'auto';
            btn.style.transform = 'translateY(-50%)';
        }
        // 初始化时保证按钮可见
        setTimeout(() => { ensureButtonInView(btn); }, 100);

        // 添加拖拽功能
        let isDragging = false;
        let dragOffsetX, dragOffsetY;
        let justDragged = false;
        let dragStartX = 0, dragStartY = 0, dragMoved = false;

        btn.addEventListener('mousedown', startDrag);
        btn.addEventListener('touchstart', startDragTouch, { passive: false });

        function startDrag(e) {
            e.preventDefault();
            isDragging = true;
            dragMoved = false;
            btn.classList.add('dragging');
            const rect = btn.getBoundingClientRect();
            dragOffsetX = e.clientX - rect.left;
            dragOffsetY = e.clientY - rect.top;
            dragStartX = e.clientX;
            dragStartY = e.clientY;

            document.addEventListener('mousemove', onDrag);
            document.addEventListener('mouseup', stopDrag);
        }

        function startDragTouch(e) {
            if (e.touches.length !== 1) return;
            e.preventDefault();
            isDragging = true;
            dragMoved = false;
            btn.classList.add('dragging');
            const touch = e.touches[0];
            const rect = btn.getBoundingClientRect();
            dragOffsetX = touch.clientX - rect.left;
            dragOffsetY = touch.clientY - rect.top;

            document.addEventListener('touchmove', onDragTouch, { passive: false });
            document.addEventListener('touchend', stopDragTouch);
        }

        function onDrag(e) {
            if (!isDragging) return;
            e.preventDefault();
            const moveX = Math.abs(e.clientX - dragStartX);
            const moveY = Math.abs(e.clientY - dragStartY);
            if (moveX > 3 || moveY > 3) dragMoved = true; // 超过3像素才算拖拽
            updatePosition(e.clientX, e.clientY);
        }

        function onDragTouch(e) {
            if (!isDragging || e.touches.length !== 1) return;
            e.preventDefault();
            const touch = e.touches[0];
            updatePosition(touch.clientX, touch.clientY);
        }

        function updatePosition(clientX, clientY) {
            const x = clientX - dragOffsetX;
            const y = clientY - dragOffsetY;

            // 限制按钮不超出屏幕边界
            const maxX = window.innerWidth - btn.offsetWidth;
            const maxY = window.innerHeight - btn.offsetHeight;

            btn.style.left = `${Math.max(0, Math.min(x, maxX))}px`;
            btn.style.top = `${Math.max(0, Math.min(y, maxY))}px`;
            btn.style.right = 'auto';
            btn.style.transform = 'none';
        }

        // 保证按钮始终可见(右侧边界)
        function ensureButtonInView(btn) {
            const rect = btn.getBoundingClientRect();
            const maxX = window.innerWidth - btn.offsetWidth;
            const maxY = window.innerHeight - btn.offsetHeight;
            let left = parseInt(btn.style.left || 0);
            let top = parseInt(btn.style.top || 0);

            let needReset = false;
            if (isNaN(left) || left < 0 || left > maxX) needReset = true;
            if (isNaN(top) || top < 0 || top > maxY) needReset = true;

            if (needReset) {
                // 重置到默认位置
                btn.style.right = '20px';
                btn.style.top = '50%';
                btn.style.left = 'auto';
                btn.style.transform = 'translateY(-50%)';
                // 清除存储的错误位置
                if (typeof GM_setValue === 'function') {
                    GM_setValue('floatButtonPosition', null);
                }
            }
        }
        window.addEventListener('resize', () => ensureButtonInView(btn));

        function stopDrag(e) {
            if (!isDragging) return;
            isDragging = false;
            btn.classList.remove('dragging');
            // 保存位置
            GM_setValue('floatButtonPosition', {
                x: parseInt(btn.style.left),
                y: parseInt(btn.style.top)
            });
            document.removeEventListener('mousemove', onDrag);
            document.removeEventListener('mouseup', stopDrag);

            if (dragMoved) {
                justDragged = true;
                setTimeout(() => { justDragged = false; }, 200);
            }
        }

        function stopDragTouch(e) {
            if (!isDragging) return;
            isDragging = false;
            btn.classList.remove('dragging');
            document.removeEventListener('touchmove', onDragTouch);
            document.removeEventListener('touchend', stopDragTouch);
            // 保存位置
            GM_setValue('floatButtonPosition', {
                x: parseInt(btn.style.left),
                y: parseInt(btn.style.top)
            });
            if (dragMoved) {
                justDragged = true;
                setTimeout(() => { justDragged = false; }, 200);
            } else {
                // 没有拖动,视为点击
                showModal();
            }
        }

        // 点击事件
        btn.addEventListener('click', function(e) {
            if (justDragged) return;
            if (!isDragging && !dragMoved) {
                showModal();
            }
        });

        document.body.appendChild(btn);
        return btn;
    }

    // 创建弹窗
    function createModal() {
        const modal = document.createElement('div');
        modal.id = 'json-tool-modal';

        modal.innerHTML = `
            <div id="json-tool-content">
                <div class="json-tool-drag-handle"></div>
                <div id="json-tool-header">
                    <div id="json-tool-title">
                        <svg viewBox="0 0 24 24">
                            <path d="M14,11H10V9h4V11z M14,8H10V6h4V8z M20,4V20H4V4H20 M20,2H4C2.9,2,2,2.9,2,4v16c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V4 C22,2.9,21.1,2,20,2L20,2z M16,15H8v-2h8V15z M16,18H8v-2h8V18z M6,15H4v2h2V15z M6,18H4v2h2V18z M6,6H4v8h2V6z"></path>
                        </svg>
                        <span>JSON秒传链接工具</span>
                    </div>
                    <div id="json-tool-header-btns">
                        <button id="json-tool-maximize" title="最大化">
                            <svg viewBox="0 0 24 24">
                                <path id="json-tool-maximize-icon" d="M3 3h7v2H5v5H3V3zm11 0h7v7h-2V5h-5V3zm7 11v7h-7v-2h5v-5h2zm-11 7H3v-7h2v5h5v2z"/>
                            </svg>
                        </button>
                        <button id="json-tool-close" title="关闭">
                            <svg viewBox="0 0 24 24">
                                <path d="M18.3 5.71a1 1 0 0 0-1.41 0L12 10.59 7.11 5.7A1 1 0 0 0 5.7 7.11L10.59 12l-4.89 4.89a1 1 0 1 0 1.41 1.41L12 13.41l4.89 4.89a1 1 0 0 0 1.41-1.41L13.41 12l4.89-4.89a1 1 0 0 0 0-1.4z"/>
                            </svg>
                        </button>
                    </div>
                </div>
                <iframe id="json-tool-iframe" src="https://123.487510.xyz/"></iframe>
            </div>
        `;

        // 关闭按钮事件
        modal.querySelector('#json-tool-close').addEventListener('click', hideModal);

        // 点击背景关闭
        modal.addEventListener('click', function(e) {
            if (e.target === modal) {
                hideModal();
            }
        });

        // ESC键关闭
        document.addEventListener('keydown', function(e) {
            if (e.key === 'Escape') {
                hideModal();
            }
        });

        // 添加弹窗拖拽功能
        let isModalDragging = false;
        let modalStartX, modalStartY;
        const dragHandle = modal.querySelector('.json-tool-drag-handle');
        const content = modal.querySelector('#json-tool-content');

        dragHandle.addEventListener('mousedown', startModalDrag);

        function startModalDrag(e) {
            isModalDragging = true;
            modalStartX = e.clientX;
            modalStartY = e.clientY;
            content.style.transition = 'none'; // 拖动时禁用过渡效果
            document.addEventListener('mousemove', onModalDrag);
            document.addEventListener('mouseup', stopModalDrag);
        }

        function onModalDrag(e) {
            if (!isModalDragging) return;
            const deltaX = e.clientX - modalStartX;
            const deltaY = e.clientY - modalStartY;
            content.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
        }

        function stopModalDrag() {
            if (!isModalDragging) return;
            isModalDragging = false;
            content.style.transition = '';
            content.style.transform = '';
            document.removeEventListener('mousemove', onModalDrag);
            document.removeEventListener('mouseup', stopModalDrag);
        }

        // 最大化/还原功能
        const maximizeBtn = modal.querySelector('#json-tool-maximize');
        const maximizeIcon = modal.querySelector('#json-tool-maximize-icon');
        const contentBox = modal.querySelector('#json-tool-content');
        let isMaximized = false;
        maximizeBtn.addEventListener('click', function() {
            isMaximized = !isMaximized;
            if (isMaximized) {
                contentBox.style.width = '100vw';
                contentBox.style.height = '100vh';
                contentBox.style.maxWidth = '100vw';
                contentBox.style.maxHeight = '100vh';
                contentBox.style.borderRadius = '0';
                contentBox.style.top = '0';
                contentBox.style.left = '0';
                contentBox.style.position = 'fixed';
                contentBox.style.zIndex = '10002';
                maximizeIcon.innerHTML = '<path d="M7 14H5v5h5v-2H7v-3zm7 3v2h5v-5h-2v3h-3zm3-10V5h-3V3h5v5h-2V7zm-7-2V3H3v5h2V5h3z"/>';
            } else {
                contentBox.style.width = '';
                contentBox.style.height = '';
                contentBox.style.maxWidth = '1200px';
                contentBox.style.maxHeight = '';
                contentBox.style.borderRadius = '16px';
                contentBox.style.top = '';
                contentBox.style.left = '';
                contentBox.style.position = '';
                contentBox.style.zIndex = '';
                maximizeIcon.innerHTML = '<path d="M3 3h7v2H5v5H3V3zm11 0h7v7h-2V5h-5V3zm7 11v7h-7v-2h5v-5h2zm-11 7H3v-7h2v5h5v2z"/>';
            }
        });

        document.body.appendChild(modal);
        return modal;
    }

    // 显示弹窗
    function showModal() {
        const modal = document.getElementById('json-tool-modal');
        if (modal) {
            document.body.style.overflow = 'hidden';
            setTimeout(() => {
                modal.classList.add('show');
            }, 10);
        }
    }

    // 隐藏弹窗
    function hideModal() {
        const modal = document.getElementById('json-tool-modal');
        if (modal) {
            modal.classList.remove('show');
            setTimeout(() => {
                document.body.style.overflow = '';
            }, 300); // 匹配动画时间
        }
    }

    // 初始化
    function init() {
        // 等待页面加载完成
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', function() {
                createFloatButton();
                createModal();
            });
        } else {
            createFloatButton();
            createModal();
        }
    }

    // 启动脚本
    init();

    // 只在油猴环境下监听 iframe 消息,实现剪贴板代理
    window.addEventListener('message', async function(event) {
        // 只允许信任的 iframe 域名
        if (!event.origin.startsWith('https://123.487510.xyz')) return;
        const { type, text } = event.data || {};
        if (type === 'copyToClipboard') {
            try {
                await navigator.clipboard.writeText(text);
                event.source.postMessage({ type: 'copyResult', success: true }, event.origin);
            } catch (e) {
                event.source.postMessage({ type: 'copyResult', success: false, error: e.message }, event.origin);
            }
        }
        if (type === 'pasteFromClipboard') {
            try {
                const clipText = await navigator.clipboard.readText();
                event.source.postMessage({ type: 'pasteResult', success: true, text: clipText }, event.origin);
            } catch (e) {
                event.source.postMessage({ type: 'pasteResult', success: false, error: e.message }, event.origin);
            }
        }
    });

    // 检查并重置按钮位置,超出边界则重置到默认
    function ensureButtonInView(btn) {
        const rect = btn.getBoundingClientRect();
        const maxX = window.innerWidth - btn.offsetWidth;
        const maxY = window.innerHeight - btn.offsetHeight;
        let left = parseInt(btn.style.left || 0);
        let top = parseInt(btn.style.top || 0);

        let needReset = false;
        if (isNaN(left) || left < 0 || left > maxX) needReset = true;
        if (isNaN(top) || top < 0 || top > maxY) needReset = true;

        if (needReset) {
            // 重置到默认位置
            btn.style.right = '20px';
            btn.style.top = '50%';
            btn.style.left = 'auto';
            btn.style.transform = 'translateY(-50%)';
            // 清除存储的错误位置
            if (typeof GM_setValue === 'function') {
                GM_setValue('floatButtonPosition', null);
            }
        }
    }

    // 监测“全部文件”菜单是否存在,动态显示/隐藏悬浮按钮
    function monitorMenuAndToggleButton() {
        // 获取悬浮按钮
        const floatBtn = document.getElementById('json-tool-float-btn');
        if (!floatBtn) return;

        function checkAndToggle() {
            // 检查是否存在“全部文件”菜单
            const menu = document.querySelector('.with-side-menu-layout-sider-menu--item.sider-menu-item.active .with-side-menu-layout-sider-menu--item-label.sider-menu-item-label');
            if (menu && menu.textContent.trim() === '全部文件') {
                ensureButtonInView(floatBtn); // 检查并重置位置
                floatBtn.style.display = '';
            } else {
                floatBtn.style.display = 'none';
            }
        }

        // 初始检查
        checkAndToggle();

        // 监听 DOM 变化
        const observer = new MutationObserver(checkAndToggle);
        observer.observe(document.body, { childList: true, subtree: true });

        // 页面切换时也检查
        window.addEventListener('hashchange', checkAndToggle);
        window.addEventListener('popstate', checkAndToggle);
    }

    // 在初始化后调用,确保按钮已创建
    setTimeout(monitorMenuAndToggleButton, 1000);
})();