123FastLink

Creat and save 123pan instant links.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         123FastLink
// @namespace    http://tampermonkey.net/
// @version      2025.7.25
// @description  Creat and save 123pan instant links.
// @author       Baoqing
// @author       Chaofan
// @author       lipkiat
// @match        *://*.123pan.com/*
// @match        *://*.123pan.cn/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=123pan.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    // ----------------------------------------------------基础环境----------------------------------------------------
    // ==================🚀 构建URL函数 ==================
    const buildURL = (host, path, queryParams) => {
        const queryString = new URLSearchParams(queryParams).toString();
        return `${host}${path}?${queryString}`;
    };

    // ==================🌐 发送请求函数 ==================
    async function sendRequest(method, path, queryParams, body) {
        const config = {
            host: 'https://' + window.location.host,
            queryParams: { // 🛡️ 预留的签名参数(可选)
                //'803521858': '1738073884-258518-2032310069'
            },
            // 🔑 获取身份认证信息
            authToken: localStorage['authorToken'],
            loginUuid: localStorage['LoginUuid'],

            appVersion: '3',
            referer: document.location.href,
        };

        const headers = {
            'Content-Type': 'application/json;charset=UTF-8',
            'Authorization': 'Bearer ' + config.authToken,
            'platform': 'web',
            'App-Version': config.appVersion,
            'LoginUuid': config.loginUuid,
            'Origin': config.host,
            'Referer': config.referer,
        };

        try {
            const response = await fetch(buildURL(config.host, path, queryParams), {
                method: method,
                headers: headers,
                body: body,
                credentials: 'include'
            });

            console.log(`[${response.status}] ${response.statusText}`);
            const data = await response.json();
            console.table(data); // ✅ 表格化输出

            if (data.code !== 0) {
                console.error('❗ 业务逻辑错误:', data.message);
                throw '❗ 业务逻辑错误:' + data.message;
            }

            return data; // ✅ 确保 sendRequest 返回 data
        } catch (error) {
            console.error('⚠️ 网络请求失败:', error);
            throw '未知错误';
            return null;
        }
    }

    // ----------------------------------------------------生成秒传----------------------------------------------------

    // ====================== 📂 获取文件信息 ================
    async function getFileInfo(idList) {
        const transformedList = idList.map(fileId => ({ fileId }));
        const responseData = await sendRequest(
            "POST",
            "/b/api/file/info", {},
            JSON.stringify({ // 请求体
                fileIdList: transformedList
            })
        );
        return responseData;
    }

    // ===================== 获取选择的文件id =============
    function getSelectFile() {
        const fileRow = Array.from(document.getElementsByClassName("ant-table-row ant-table-row-level-0 editable-row"));
        const selectFile = fileRow.map(function(element, index, array) {
            if (element.getElementsByTagName("input")[0].checked) {
                return element.getAttribute('data-row-key'); // 返回修改后的元素
            }
        }).filter(item => item != null);
        return selectFile;
    }

    // ====================🔗 生成秒传链接 ===================
    async function creatShareLink() {
        const fileSelect = getSelectFile();
        //console.log("fileS", fileSelect);
        if (!fileSelect.length) {
            return ""
        }
        const fileInfo = Array.from((await getFileInfo(fileSelect))['data']['infoList']);
        var hasFloder = 0;
        const shareLink = fileInfo.map(function(info, infoIndex, infoArray) { //.filter(item => item != null)
            if (info.Type == 0) {
                return [info.Etag, info.Size, info.FileName.replace("#", "").replace("$", "")].join('#');
            } else {
                console.log("忽略文件夹", info.FileName);
                hasFloder = 1;
            }
        }).filter(item => item != null).join('\n');
        if (hasFloder) {
            alert("文件夹无法秒传,将被忽略");
        }
        return shareLink;
    }


    // ----------------------------------------------------接受秒传----------------------------------------------------
    // ==================📥 参数解析 ====================
    function getShareFileInfo(shareLink) {
        const shareLinkList = Array.from(shareLink.replace(/\r?\n/g, '$').split('$'));
        const shareFileInfoList = shareLinkList.map(function(singleShareLink, linkIndex, linkArray) {
            const singleFileInfoList = singleShareLink.split('#');
            if (singleFileInfoList.length < 3) {
                return null;
            }
            const singleFileInfo = {
                etag: singleFileInfoList[0],
                size: singleFileInfoList[1],
                fileName: singleFileInfoList[2]
            };
            return singleFileInfo;
        });
        return shareFileInfoList;
        //JSON.parse(decodeURIComponent(atob(shareLink)));
    }

    // ================== 获取单一文件 ===================
    async function getSingleFile(shareFileInfo) {
        // --------------------- 文件信息 ---------------------
        const fileInfo = {
            driveId: 0,
            etag: shareFileInfo.etag,
            fileName: shareFileInfo.fileName,
            parentFileId: JSON.parse(sessionStorage['filePath'])['homeFilePath'][0] || 0,
            size: shareFileInfo.size,
            type: 0,
            duplicate: 1
        };
        // --------------------- 发送请求 ---------------------
        const responseData = await sendRequest('POST', '/b/api/file/upload_request', {},
            JSON.stringify({...fileInfo, RequestSource: null }));
        return responseData;
    }

    // ================== 获取全部文件 ===================
    async function getFiles(shareLink) {
        try {
            const shareFileList = getShareFileInfo(shareLink);
            for (var i = 0; i < shareFileList.length; i++) {
                const fileInfo = shareFileList[i];
                if (fileInfo === null) {
                    continue; // 跳过空行
                }
                getSingleFile(fileInfo);
            }
            return 1
        } catch {
            return 0
        }
    }

    // =================✨ 弹出操作框 ================
    function showCopyModal(defaultText = "", buttonText = "复制", buttonFunction = copyToClipboard) {
        // 这个样式会遮挡,清除掉
        const floatTable = document.getElementsByClassName('ant-table-header ant-table-sticky-holder');
        if (floatTable.length > 0) {
            floatTable[0].className = "ant-table-header";
        }

        // 检查是否已有样式,防止重复添加
        if (!document.getElementById("modal-style")) {
            let style = document.createElement("style");
            style.id = "modal-style";
            style.innerHTML = `
            .modal-overlay {display: flex;position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(0, 0, 0, 0.5);justify-content: center;align-items: center;z-index: 2; }
            .modal {background: white;padding: 20px;border-radius: 8px;box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);text-align: center;width: 300px;}
            .close-btn {background: #f44336;color: white;border: none;padding: 5px 10px;cursor: pointer;float: right;}
            .modal textarea {width: 90%;padding: 8px;margin: 10px 0;border: 1px solid #ccc;border-radius: 4px;resize: vertical;min-height: 100px;}
            .copy-btn {background: #4CAF50;color: white;border: none;padding: 8px 12px;cursor: pointer;border-radius: 4px;}
        `;
            document.head.appendChild(style);
        }

        // 如果已有弹窗,则删除它
        let existingModal = document.getElementById('modal');
        if (existingModal) existingModal.remove();

        // 创建遮罩层
        let modalOverlay = document.createElement('div');
        modalOverlay.className = 'modal-overlay';
        modalOverlay.id = 'modal';

        // 创建弹窗
        modalOverlay.innerHTML = `
        <div class="modal">
            <button class="close-btn" onclick="document.getElementById('modal').remove()">×</button>
            <h3>秒传链接</h3>
            <textarea id="copyText">${defaultText}</textarea>
            <button class="copy-btn" id="massageboxButton" onclick="${buttonFunction}()">${buttonText}</button>
        </div>
    `;

        // 绑定接受直链按钮事件
        modalOverlay.querySelector('#massageboxButton').addEventListener('click', () => {
            buttonFunction();
        });

        // 添加到 body
        document.body.appendChild(modalOverlay);
    }

    // ===================📋 写入剪贴板 ====================
    async function copyToClipboard() {
        try {
            const inputField = document.getElementById('copyText');
            if (!inputField) {
                throw new Error('找不到输入框元素');
            }

            // 使用现代 Clipboard API
            await navigator.clipboard.writeText(inputField.value);

            // 更新提示信息
            alert('已成功复制到剪贴板 📋');
        } catch (err) {
            console.error('复制失败:', err);
            alert(`复制失败: ${err.message || '请手动复制内容'}`);
        }
    }

    // ================== 获取文件 ====================
    function startGetFile() {
        const shareLink = document.getElementById("copyText").value;
        if (getFiles(shareLink)) {
            //alert("获取成功,请刷新目录查看,如没有请检查根目录。");
            // 如果已有弹窗,则删除它
            let existingModal = document.getElementById('modal');
            if (existingModal) existingModal.remove();
            //模拟点击class="layout-operate-icon mfy-tooltip"的div下的svg元素
            const element = document.querySelector('.layout-operate-icon.mfy-tooltip svg');
            // 创建鼠标点击事件
            const clickEvent = new MouseEvent('click', {
                bubbles: true,    // 事件是否冒泡
                cancelable: true, // 事件能否被取消
                view: window      // 关联的视图(通常是 window)
            });
            // 分发事件到元素
            element.dispatchEvent(clickEvent);
        } else {
            alert("获取失败");
        }
    }

    // ================== 添加秒传按钮的主方法 ================== 
    function addButton() {
        //判断是否已经存在按钮了
        const buttonExist = document.querySelector('.mfy-button-container');
        if (buttonExist) return;

        //判断是否在文件列表页面
        const isFilePage = window.location.pathname == "/" && (window.location.search == "" || window.location.search.includes("homeFilePath"));
        if (!isFilePage) return;

        // 查找目标容器
        const container = document.querySelector('.home-operator-button-group');
        if (!container) return;

        // 创建按钮容器
        const btnContainer = document.createElement('div');
        btnContainer.className = 'mfy-button-container';
        btnContainer.style.position = 'relative';
        btnContainer.style.display = 'inline-block';

        // 创建按钮
        const btn = document.createElement('button');
        btn.className = 'ant-btn css-dev-only-do-not-override-168k93g ant-btn-default ant-btn-color-default ant-btn-variant-outlined mfy-button create-button';
        btn.style.background = "#4CAF50";
        btn.style.color = "#fff";
        btn.style.border = "none";
        btn.innerHTML = `
            <svg t="1753345987410" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2781"
                width="16" height="16">
                <path
                    d="M395.765333 586.570667h-171.733333c-22.421333 0-37.888-22.442667-29.909333-43.381334L364.768 95.274667A32 32 0 0 1 394.666667 74.666667h287.957333c22.72 0 38.208 23.018667 29.632 44.064l-99.36 243.882666h187.050667c27.509333 0 42.186667 32.426667 24.042666 53.098667l-458.602666 522.56c-22.293333 25.408-63.626667 3.392-54.976-29.28l85.354666-322.421333z"
                    fill="#ffffff" p-id="2782"></path>
            </svg>
            <span>秒传</span>
        `;

        // 创建下拉菜单
        const dropdown = document.createElement('div');
        dropdown.className = 'mfy-dropdown';
        dropdown.style.display = 'none';
        dropdown.style.position = 'absolute';
        dropdown.style.top = 'calc(100% + 5px)'; // 增加5px间距
        dropdown.style.left = '0';
        dropdown.style.backgroundColor = '#fff';
        dropdown.style.border = '1px solid #d9d9d9';
        dropdown.style.borderRadius = '10px'; // 圆角调整为10px
        dropdown.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
        dropdown.style.zIndex = '1000';
        dropdown.style.minWidth = '120px';
        dropdown.style.overflow = 'hidden'; // 确保圆角效果

        dropdown.innerHTML = `
            <div class="mfy-dropdown-item" data-action="generate">生成秒传连接</div>
            <div class="mfy-dropdown-item" data-action="save">保存秒传连接</div>
        `;

        // 添加样式
        const style = document.createElement('style');
        style.textContent = `
            .mfy-button-container:hover .mfy-dropdown {
                display: block !important;
            }
            .mfy-dropdown-item {
                padding: 8px 12px;
                cursor: pointer;
                transition: background 0.3s;
                font-size: 14px;
            }
            .mfy-dropdown-item:hover {
                background-color: #f5f5f5;
            }
            /* 添加下拉菜单顶部间距 */
            .mfy-dropdown::before {
                content: '';
                position: absolute;
                top: -5px; /* 填补间距 */
                left: 0;
                width: 100%;
                height: 5px;
                background: transparent;
            }
        `;
        document.head.appendChild(style);

        // 组装元素
        btnContainer.appendChild(btn);
        btnContainer.appendChild(dropdown);
        container.insertBefore(btnContainer, container.firstChild);

        // 添加下拉菜单点击事件
        dropdown.querySelectorAll('.mfy-dropdown-item').forEach(item => {
            item.addEventListener('click', async function() {
                const action = this.dataset.action;
                if (action === 'generate') {
                    // 这里添加生成秒传连接的具体逻辑
                    const shareLink = await creatShareLink();
                    if (shareLink == '') {
                        alert("没有选择文件");
                        return
                    }
                    showCopyModal(shareLink);
                } else if (action === 'save') {
                    // 这里添加保存秒传连接的具体逻辑
                    showCopyModal("", "获取", startGetFile);
                }
                dropdown.style.display = 'none';
            });
        });

        // 鼠标事件处理
        btnContainer.addEventListener('mouseenter', function() {
            dropdown.style.display = 'block';
        });

        btnContainer.addEventListener('mouseleave', function() {
            let timer;
            clearTimeout(timer); // 清除已有定时器避免冲突
            timer = setTimeout(() => {
                dropdown.style.display = 'none';
            }, 300); // 10毫秒延迟
        });
    }

    // 等待页面加载完成创建秒传按钮
    window.addEventListener('load', addButton);

    // 下面的是为了监测地址栏发生变化时,重新添加秒传按钮,主要是为了解决当跳转到其他页面(如分享页)再跳转回全部文件页面时,秒传按钮不再加载的BUG
    // 虽然只要地址栏发生变化就会触发添加秒传按钮,但是在添加按钮的方法中判断了当前的页面是否是全部文件页面,只有在全部文件页面上才会继续添加
    const originalPushState = history.pushState;
    const originalReplaceState = history.replaceState;

    history.pushState = function () {
        originalPushState.apply(this, arguments);
        triggerUrlChange();
    };

    history.replaceState = function () {
        originalReplaceState.apply(this, arguments);
        triggerUrlChange();
    };

    // 监听浏览器前进/后退
    window.addEventListener('popstate', triggerUrlChange);

    // 统一触发函数
    function triggerUrlChange() {
        //此处必须有延迟,否则秒传按钮不会显示
        setTimeout(addButton, 10);
    }

})();