Greasy Fork

来自缓存

Greasy Fork is available in English.

115云盘磁力链接助手-- 天黑了

自动捕捉页面磁力链接并保存至115云盘, 可选择已有文件夹保存

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         115云盘磁力链接助手-- 天黑了
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  自动捕捉页面磁力链接并保存至115云盘, 可选择已有文件夹保存
// @author       天黑了
// @license      MIT
// @match        *://*/*
// @connect      115.com
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_log
// @grant        window.Notification
// @run-at       document-end
// @homepage     https://github.com/tianheil3/115-magnet-helper
// @supportURL   https://github.com/tianheil3/115-magnet-helper/issues
// ==/UserScript==

(function() {
    'use strict';
    
    console.log('115云盘磁力链接助手已加载 (v1.5)');
    
    // 调试函数
    function debug(msg, ...args) {
        console.log(`[115助手] ${msg}`, ...args);
    }

    // 匹配磁力链接的正则表达式
    const magnetRegex = /magnet:\?xt=urn:btih:[a-zA-Z0-9]{32,40}/gi;

    // 修改115图标的SVG,使用文字"115"
    const icon115 = `
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
        <text x="50%" y="50%" text-anchor="middle" dominant-baseline="central" 
            fill="white" font-family="Arial" font-weight="bold" font-size="10">115</text>
    </svg>`;

    // 修改按钮样式,移除定位相关的属性
    const buttonStyle = `
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 26px;
        height: 26px;
        background-color: #2777F8;
        border-radius: 50%;
        cursor: pointer;
        margin-left: 5px;
        color: white;
        font-family: Arial, sans-serif;
        font-size: 11px;
        font-weight: bold;
        box-shadow: 0 2px 5px rgba(0,0,0,0.2);
        transition: all 0.3s ease;
        opacity: 0.9;
        user-select: none;
        vertical-align: middle;
    `;

    // 存储已创建的按钮
    const createdButtons = new Set();

    // 创建一个通用的通知函数
    function showNotification(title, text, isWarning = false) {
        debug('准备显示通知:', { title, text, isWarning });
        
        // 直接使用 alert 显示通知
        setTimeout(() => {
            window.alert(`${title}\n${text}`);
        }, 100);

        // 同时尝试使用 GM_notification
        try {
            GM_notification({
                title: title,
                text: text,
                timeout: isWarning ? 3000 : 5000,
                onclick: () => debug('通知被点击了')
            });
            debug('GM_notification 已调用');
        } catch (e) {
            debug('GM_notification 调用失败:', e);
        }
    }

    // 解析磁力链接中的 dn 参数
    function getDisplayNameFromMagnet(magnetLink) {
        try {
            const urlParams = new URLSearchParams(magnetLink.substring(magnetLink.indexOf('?') + 1));
            const dn = urlParams.get('dn');
            if (dn) {
                // 解码并清理非法字符
                let decodedDn = decodeURIComponent(dn.replace(/\+/g, ' '));
                // 移除 Windows 文件名非法字符: \ / : * ? " < > |
                decodedDn = decodedDn.replace(/[\\/:*?"<>|]/g, '_');
                // 移除控制字符
                decodedDn = decodedDn.replace(/[\x00-\x1F\x7F]/g, '');
                // 移除首尾空格
                decodedDn = decodedDn.trim();
                // 避免文件名过长(115 可能有限制,暂定 200)
                return decodedDn.substring(0, 200);
            }
        } catch (e) {
            debug('解析 dn 参数失败:', e);
        }
        return null; // 如果没有 dn 参数或解析失败,返回 null
    }

    // 获取 115 文件夹列表 (目前只获取根目录下的)
    async function get115Folders() {
        return new Promise((resolve) => {
            debug('开始获取根目录文件夹列表');
            // 尝试简化 URL 参数,并减少 limit
            const apiUrl = 'https://aps.115.com/natsort/files.php?aid=1&cid=0&offset=0&limit=300&show_dir=1&natsort=1&format=json';
            GM_xmlhttpRequest({
                method: 'GET',
                // 使用 115 Web API 获取文件列表,cid=0 表示根目录
                // 参数可能随版本变化,limit 设置大一些以获取更多文件夹
                url: apiUrl, 
                headers: {
                    'Accept': 'application/json, text/javascript, */*; q=0.01',
                    'Referer': 'https://115.com/',
                    'User-Agent': window.navigator.userAgent
                },
                withCredentials: true,
                onload: function(response) {
                    try {
                        debug('获取文件夹列表 API 响应:', response.responseText.substring(0, 500) + '...'); // 避免日志过长
                        const result = JSON.parse(response.responseText);
                        if (result.state) {
                            // 115 API 返回的数据结构可能变化,这里尝试兼容常见的文件夹判断方式
                            const folders = result.data
                                // 主要判断方式:查找具有 cid (文件夹ID) 且 n (名称) 存在的项
                                // 可能需要结合其他字段,如 ico == 'folder',或检查是否存在 pid (父ID)
                                // 更可靠的判断:有 cid 和 n,但没有 fid (文件ID) 和 sha1 (文件哈希)
                                .filter(item => item.cid && item.n && typeof item.fid === 'undefined' && typeof item.sha1 === 'undefined')
                                .map(item => ({ id: item.cid, name: item.n }));
                            debug('成功获取文件夹列表:', folders.length, '个');
                            resolve(folders); // 返回 {id, name} 数组
                        } else {
                            // 改进错误日志,包含 errNo
                            const errorDetail = `errNo: ${result.errNo}, error: "${result.error || ''}", msg: "${result.msg || 'N/A'}"`;
                            console.error(`获取文件夹列表失败: API返回 state:false, ${errorDetail}`);
                            resolve([]); // 返回空数组
                        }
                    } catch (error) {
                        console.error('解析文件夹列表响应失败:', error, response.responseText);
                        resolve([]); // 解析失败返回空数组
                    }
                },
                onerror: function(error) {
                    console.error('获取文件夹列表请求失败:', error);
                    resolve([]); // 请求失败返回空数组
                }
            });
        });
    }

    // 显示文件夹选择模态框
    async function showFolderSelector(magnetLink, buttonElement) {
        // --- 创建模态框基础结构 ---
        const modalOverlay = document.createElement('div');
        modalOverlay.id = 'magnet-helper-modal-overlay'; // 添加 ID 以便查找和移除
        modalOverlay.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background-color: rgba(0, 0, 0, 0.6); z-index: 9999;
            display: flex; justify-content: center; align-items: center;
        `;

        const modalContent = document.createElement('div');
        modalContent.style.cssText = `
            background-color: white; padding: 25px; border-radius: 8px;
            min-width: 300px; max-width: 80%; max-height: 80%;
            overflow-y: auto; box-shadow: 0 5px 15px rgba(0,0,0,0.3);
            color: #333; font-family: sans-serif; font-size: 14px;
        `;

        const title = document.createElement('h3');
        title.textContent = '选择保存位置';
        title.style.cssText = 'margin-top: 0; margin-bottom: 15px; color: #1E5AC8; border-bottom: 1px solid #eee; padding-bottom: 10px;';
        modalContent.appendChild(title);

        const loadingText = document.createElement('p');
        loadingText.textContent = '正在加载文件夹列表...';
        modalContent.appendChild(loadingText);

        modalOverlay.appendChild(modalContent);
        document.body.appendChild(modalOverlay);

        // --- 获取并显示文件夹 ---
        try {
            const folders = await get115Folders();
            if (modalContent.contains(loadingText)) {
                 modalContent.removeChild(loadingText); // 移除加载提示
            }

            const list = document.createElement('ul');
            list.style.cssText = 'list-style: none; padding: 0; margin: 0 0 15px 0; max-height: 300px; overflow-y: auto;';

            // 添加 "根目录" 选项
            const rootOption = document.createElement('li');
            rootOption.textContent = '根目录 (默认)';
            rootOption.style.cssText = 'padding: 8px 12px; cursor: pointer; border-radius: 4px; margin-bottom: 5px; background-color: #f0f0f0;';
            rootOption.addEventListener('mouseover', () => { rootOption.style.backgroundColor = '#e0e0e0'; });
            rootOption.addEventListener('mouseout', () => { rootOption.style.backgroundColor = '#f0f0f0'; });
            rootOption.addEventListener('click', () => {
                selectFolder(0); // 根目录 ID 为 0
            });
            list.appendChild(rootOption);

            // 添加获取到的文件夹
            folders.forEach(folder => {
                const item = document.createElement('li');
                item.textContent = folder.name;
                item.title = folder.name; // 防止名称过长显示不全
                item.style.cssText = 'padding: 8px 12px; cursor: pointer; border-radius: 4px; margin-bottom: 5px; background-color: #f9f9f9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;';
                 item.addEventListener('mouseover', () => { item.style.backgroundColor = '#eee'; });
                 item.addEventListener('mouseout', () => { item.style.backgroundColor = '#f9f9f9'; });
                item.addEventListener('click', () => {
                    selectFolder(folder.id);
                });
                list.appendChild(item);
            });
            modalContent.appendChild(list);

        } catch (error) { // 网络或其他错误导致 get115Folders reject
            if (modalContent.contains(loadingText)) {
                modalContent.removeChild(loadingText);
            }
            const errorText = document.createElement('p');
            errorText.textContent = '加载文件夹列表失败!将尝试保存到根目录。' + (error.message ? `(${error.message})` : '');
            errorText.style.color = 'red';
            modalContent.appendChild(errorText);
            // 自动选择根目录并关闭
            setTimeout(() => selectFolder(0), 2500);
        }

        // --- 添加取消按钮 ---
        const cancelButton = document.createElement('button');
        cancelButton.textContent = '取消';
        cancelButton.style.cssText = `
            padding: 8px 15px; background-color: #ccc; color: #333;
            border: none; border-radius: 4px; cursor: pointer; float: right;
        `;
        cancelButton.addEventListener('click', closeAndCancel);
        modalContent.appendChild(cancelButton);

        // --- 点击遮罩层关闭 ---
        modalOverlay.addEventListener('click', (e) => {
            if (e.target === modalOverlay) {
                closeAndCancel();
            }
        });

        // --- 关闭模态框的通用函数 ---
        function closeModal() {
            setTimeout(() => { // Add delay
                const existingModal = document.getElementById('magnet-helper-modal-overlay');
                if (existingModal && existingModal.parentNode) {
                    existingModal.parentNode.removeChild(existingModal);
                }
            }, 100); // Delay of 100ms
        }

        // --- 选择文件夹并关闭模态框的函数 ---
        async function selectFolder(folderId) {
            closeModal();
            debug(`用户选择文件夹 ID: ${folderId}`);
            buttonElement.textContent = '...'; // 再次确认按钮是加载状态
            buttonElement.style.backgroundColor = '#ff9800';
            // 调用保存函数,并传递按钮元素用于状态恢复
            const success = await saveTo115(magnetLink, folderId, buttonElement);
            // 状态恢复在 saveTo115 内部处理
        }

        // --- 关闭模态框并不执行操作 ---
        function closeAndCancel() {
            closeModal();
            debug('用户取消选择');
            // 恢复按钮状态
            buttonElement.textContent = '115';
            buttonElement.style.backgroundColor = '#2777F8';
        }
    }

    // 保存到115云盘
    async function saveTo115(magnetLink, targetFolderId = 0, buttonElement = null) {
        let success = false;
        let isWarning = false;
        try {
            // 检查登录状态
            const checkLogin = () => {
                return new Promise((resolve) => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: 'https://115.com/?ct=offline&ac=space',
                        headers: {
                            'Accept': 'application/json',
                            'Referer': 'https://115.com/',
                            'User-Agent': window.navigator.userAgent
                        },
                        withCredentials: true,
                        onload: function(response) {
                            try {
                                const data = JSON.parse(response.responseText);
                                resolve(data.state);
                            } catch (error) {
                                resolve(false);
                            }
                        },
                        onerror: () => resolve(false)
                    });
                });
            };

            // 获取离线空间和用户ID
            const getOfflineSpace = () => {
                return new Promise((resolve) => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: 'https://115.com/?ct=offline&ac=space',
                        headers: {
                            'Accept': 'application/json',
                            'Referer': 'https://115.com/'
                        },
                        withCredentials: true,
                        onload: function(response) {
                            try {
                                const data = JSON.parse(response.responseText);
                                resolve(data);
                            } catch (error) {
                                resolve(null);
                            }
                        },
                        onerror: () => resolve(null)
                    });
                });
            };

            // 检查登录状态
            const isLoggedIn = await checkLogin();
            if (!isLoggedIn) {
                GM_notification({
                    text: '请先登录115云盘',
                    title: '115云盘助手',
                    timeout: 3000
                });
                window.open('https://115.com/?ct=login', '_blank');
                return false;
            }

            // 获取离线空间信息
            const spaceInfo = await getOfflineSpace();
            if (!spaceInfo || !spaceInfo.state) {
                debug('获取离线空间信息失败,但仍尝试添加任务');
            }

            // 添加离线任务,并指定目标文件夹ID (wp_path_id)
            return new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: 'POST',
                    url: 'https://115.com/web/lixian/?ct=lixian&ac=add_task_url',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                        'Referer': 'https://115.com/',
                        'Origin': 'https://115.com',
                        'User-Agent': window.navigator.userAgent
                    },
                    // 在 data 中添加 wp_path_id 参数
                    data: `url=${encodeURIComponent(magnetLink)}&wp_path_id=${targetFolderId}`,
                    withCredentials: true,
                    onload: function(response) {
                        try {
                            // 增加详细的调试信息
                            debug('API响应:', response.responseText);
                            debug('响应状态:', response.status);
                            
                            const result = JSON.parse(response.responseText);
                            debug('解析后的结果:', {
                                state: result.state,
                                errtype: result.errtype,
                                errcode: result.errcode,
                                errno: result.errno,
                                error_msg: result.error_msg
                            });
                            
                            success = result.state;
                            isWarning = result.errtype === 'war' || result.errcode === 10008; // 任务已存在算警告

                            if (success) {
                                showNotification(
                                    '115云盘助手',
                                    '磁力链接已成功添加到离线下载队列',
                                    true
                                );
                                resolve(isWarning); // 失败时,如果是警告也算某种程度的"成功"
                            } else {
                                let errorMessage = '添加任务失败';
                                
                                // 优先使用 error_msg
                                if (result.error_msg) {
                                    errorMessage = result.error_msg;
                                    debug('使用 error_msg 作为错误信息:', errorMessage);
                                } else {
                                    const errorCode = result.errcode || result.errno;
                                    debug('使用错误代码:', errorCode);
                                    
                                    const errorTypes = {
                                        911: '用户未登录',
                                        10008: '任务已存在',
                                        10009: '任务超出限制',
                                        10004: '空间不足',
                                        10002: '解析失败',
                                    };

                                    if (errorCode && errorTypes[errorCode]) {
                                        errorMessage = errorTypes[errorCode];
                                        debug('从错误类型映射获取错误信息:', errorMessage);
                                    }
                                }

                                // 检查是否为警告类型
                                debug('是否为警告类型:', isWarning, '(errtype:', result.errtype, 'errcode:', result.errcode, ')');

                                // 显示通知
                                showNotification(
                                    isWarning ? '115云盘助手 - 提示' : '115云盘助手 - 错误',
                                    errorMessage,
                                    isWarning
                                );

                                resolve(isWarning);
                            }
                        } catch (error) {
                            success = false;
                            console.error('解析响应失败:', error, response.responseText);
                            GM_notification({
                                text: '添加任务失败: ' + (error.message || '未知错误'),
                                title: '115云盘助手',
                                timeout: 3000
                            });
                            resolve(false);
                        }
                    },
                    onerror: function(error) {
                        success = false;
                        console.error('请求失败:', error);
                        GM_notification({
                            text: '网络请求失败',
                            title: '115云盘助手',
                            timeout: 3000
                        });
                        resolve(false);
                    },
                    // GM_xmlhttpRequest 的 finally 不可靠,在 onload 和 onerror 中处理
                    onloadend: function() {
                        // 恢复按钮状态
                        if (buttonElement) {
                           debug('恢复按钮状态, success:', success, 'isWarning:', isWarning);
                           buttonElement.textContent = '115';
                           // 成功或警告(任务已存在) 都用蓝色,否则用红色
                           buttonElement.style.backgroundColor = (success || isWarning) ? '#2777F8' : '#f44336';
                           if (!(success || isWarning)) { // 如果是彻底失败,一段时间后恢复蓝色
                               setTimeout(() => {
                                   if (buttonElement.style.backgroundColor === 'rgb(244, 67, 54)') { // 检查是否仍是红色
                                      buttonElement.style.backgroundColor = '#2777F8';
                                   }
                               }, 2000);
                           }
                        }
                    }
                });
            });
        } catch (error) {
            success = false;
            console.error('保存到115云盘外层失败:', error);
            GM_notification({
                text: '保存失败:' + error.message,
                title: '115云盘助手',
                timeout: 3000
            });
             // 恢复按钮状态 (如果需要)
             if (buttonElement) {
                 buttonElement.textContent = '115';
                 buttonElement.style.backgroundColor = '#f44336'; // 红色表示错误
                 setTimeout(() => {
                     if (buttonElement.style.backgroundColor === 'rgb(244, 67, 54)') {
                          buttonElement.style.backgroundColor = '#2777F8';
                     }
                 }, 2000);
             }
            return false;
        }
    }

    // 创建磁力链接按钮
    function createMagnetButton(magnetLink, element) {
        if (createdButtons.has(magnetLink)) return;
        debug('创建按钮:', magnetLink);

        // 创建一个包装容器
        const wrapper = document.createElement('span');
        wrapper.style.cssText = `
            display: inline-flex;
            align-items: center;
            white-space: nowrap;
            margin: 0 2px;
        `;

        // 创建按钮 - 改名为 buttonElement
        const buttonElement = document.createElement('span');
        buttonElement.innerHTML = '115';
        buttonElement.style.cssText = buttonStyle;
        buttonElement.title = '点击保存到115云盘';

        if (element.nodeType === Node.TEXT_NODE) {
            // 处理文本节点
            const text = element.textContent;
            const index = text.indexOf(magnetLink);
            if (index !== -1) {
                const beforeText = document.createTextNode(text.substring(0, index));
                const afterText = document.createTextNode(text.substring(index + magnetLink.length));
                const magnetSpan = document.createElement('span');
                magnetSpan.textContent = magnetLink;
                
                const parent = element.parentNode;
                parent.insertBefore(beforeText, element);
                parent.insertBefore(wrapper, element);
                wrapper.appendChild(magnetSpan);
                wrapper.appendChild(buttonElement);
                parent.insertBefore(afterText, element);
                parent.removeChild(element);
            }
        } else {
            // 处理元素节点
            if (element.tagName === 'A' || element.tagName === 'INPUT') {
                element.parentNode.insertBefore(wrapper, element.nextSibling);
                wrapper.appendChild(buttonElement);
            } else {
                element.appendChild(wrapper);
                wrapper.appendChild(buttonElement);
            }
        }

        // 添加按钮事件处理 - 直接使用 buttonElement
        if (buttonElement) {
            // 添加交互效果
            buttonElement.addEventListener('mouseenter', () => {
                buttonElement.style.transform = 'scale(1.1)';
                buttonElement.style.opacity = '1';
            });
            
            buttonElement.addEventListener('mouseleave', () => {
                buttonElement.style.transform = 'scale(1)';
                buttonElement.style.opacity = '0.9';
            });
            
            // 点击处理
            buttonElement.addEventListener('click', async (e) => {
                e.stopPropagation();
                e.preventDefault();
                debug('点击按钮,准备显示文件夹选择器:', magnetLink);

                // 改变按钮外观,表示正在处理
                buttonElement.textContent = '...';
                buttonElement.style.backgroundColor = '#ff9800'; // 橙色表示等待
                buttonElement.disabled = true; // 暂时禁用按钮防止重复点击

                // 显示文件夹选择器,传递按钮元素以便后续恢复状态
                try {
                    await showFolderSelector(magnetLink, buttonElement);
                    // 选择器内部会调用 saveTo115 并处理后续状态
                } catch (error) {
                    console.error('显示文件夹选择器时出错:', error);
                    // 如果选择器本身出错,恢复按钮
                    buttonElement.textContent = '115';
                    buttonElement.style.backgroundColor = '#f44336'; // 显示错误
                    setTimeout(() => {
                          buttonElement.style.backgroundColor = '#2777F8';
                     }, 2000);
                } finally {
                    buttonElement.disabled = false; // 无论如何最终都恢复按钮可用性
                }
            });
        }

        createdButtons.add(magnetLink);
    }

    // 查找并处理磁力链接
    function findAndProcessMagnetLinks() {
        debug('开始查找磁力链接');
        
        // 使用 TreeWalker 遍历所有文本节点
        const processedLinks = new Set();
        const walker = document.createTreeWalker(
            document.body,
            NodeFilter.SHOW_TEXT,
            {
                acceptNode: function(node) {
                    // 过滤掉不可见元素和脚本标签
                    const parent = node.parentElement;
                    if (!parent || 
                        parent.tagName === 'SCRIPT' || 
                        parent.tagName === 'STYLE' || 
                        parent.tagName === 'NOSCRIPT' ||
                        getComputedStyle(parent).display === 'none' ||
                        getComputedStyle(parent).visibility === 'hidden') {
                        return NodeFilter.FILTER_REJECT;
                    }
                    // 只接受包含磁力链接的文本节点
                    return node.textContent.includes('magnet:?') ? 
                        NodeFilter.FILTER_ACCEPT : 
                        NodeFilter.FILTER_SKIP;
                }
            }
        );

        const textNodes = [];
        while (walker.nextNode()) {
            textNodes.push(walker.currentNode);
        }

        // 处理找到的文本节点
        textNodes.forEach(node => {
            const matches = node.textContent.match(magnetRegex);
            if (matches) {
                matches.forEach(magnetLink => {
                    if (!processedLinks.has(magnetLink)) {
                        // 找到实际包含磁力链接的最小父元素
                        let targetElement = node;
                        let parent = node.parentElement;
                        while (parent && parent !== document.body) {
                            if (parent.textContent.trim() === node.textContent.trim()) {
                                targetElement = parent;
                                parent = parent.parentElement;
                            } else {
                                break;
                            }
                        }
                        createMagnetButton(magnetLink, targetElement);
                        processedLinks.add(magnetLink);
                    }
                });
            }
        });

        // 检查特殊属性(如链接和输入框)
        const elements = document.querySelectorAll('a[href], input[value], [data-url], [title], [data-clipboard-text]');
        elements.forEach(element => {
            const attributes = ['href', 'data-url', 'value', 'title', 'data-clipboard-text'];
            for (const attr of attributes) {
                const value = element.getAttribute(attr);
                if (value) {
                    const matches = value.match(magnetRegex);
                    if (matches) {
                        matches.forEach(magnetLink => {
                            if (!processedLinks.has(magnetLink)) {
                                createMagnetButton(magnetLink, element);
                                processedLinks.add(magnetLink);
                            }
                        });
                    }
                }
            }
        });
    }

    // 初始化
    function init() {
        debug('初始化脚本');
        findAndProcessMagnetLinks();

        // 使用 MutationObserver 监听页面变化
        const observer = new MutationObserver(() => {
            setTimeout(findAndProcessMagnetLinks, 500);
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

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