Greasy Fork

一键复制磁力链和推送到115离线

目前支持BT4G/BTDig/BTSOW/SOBT/BTMulu/Nyaa/DMHY/CLM(磁力链搜索引擎)添加一键复制磁力链和推送到115网盘进行离线(推送离线任务需当前浏览器已登录115会员账号)

// ==UserScript==
// @name         一键复制磁力链和推送到115离线
// @author       [email protected]
// @description  目前支持BT4G/BTDig/BTSOW/SOBT/BTMulu/Nyaa/DMHY/CLM(磁力链搜索引擎)添加一键复制磁力链和推送到115网盘进行离线(推送离线任务需当前浏览器已登录115会员账号)
// @version      1.0.9.20250423
// @icon         https://github.githubassets.com/assets/mona-loading-default-c3c7aad1282f.gif
// @include      https://bt4gprx.com/*
// @include      https://btdig.com/*
// @include      https://www.btdig.com/*
// @include      https://sobt*.*/*
// @include      https://nyaa.si/*
// @include      https://btsow.*/*
// @include      https://cl*.cl*/*
// @match        https://*.btmulu.work/*
// @match        https://*.dmhy.org/*
// @grant        GM_setClipboard
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @connect      115.com
// @connect      login.115.com
// @connect      *
// @run-at       document-end
// @namespace    https://greasyfork.org/users/1453515
// @license      MIT

// ==/UserScript==

(function() {
    'use strict';

    // 移动设备检测
    const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

    // 配置参数
    const CONFIG = {
        notificationTimeout: isMobile ? 5000 : 3000,
        retryCount: 3,
        retryDelay: 1000,
        cookieRefreshInterval: 30 * 60 * 1000 // 30分钟刷新一次Cookie
    };

    // 错误码映射
    const ERROR_CODES = {
        10008: '任务已存在,无需重复添加',
        911: '需要账号验证,请确保已登录115会员账号',
        990: '任务包含违规内容,无法添加',
        991: '服务器繁忙,请稍后再试',
        992: '离线下载配额已用完',
        993: '当前账号无权使用离线下载功能',
        994: '文件大小超过限制',
        995: '不支持的链接类型',
        996: '网络错误,请检查连接',
        997: '服务器内部错误',
        998: '请求超时',
        999: '未知错误'
    };

    // 初始化脚本
    initScript();

    function initScript() {
        // 添加Tampermonkey菜单命令
        GM_registerMenuCommand("检查115登录状态", async () => {
            const isLoggedIn = await check115Login(true);
            showNotification('115状态', isLoggedIn ? '已登录' : '未登录');

            if (!isLoggedIn) {
                setTimeout(() => {
                    if (confirm('需要登录115网盘,是否进入115网盘登录页面?')) {
                        window.open("https://115.com/?mode=login", "_blank");
                    }
                }, 500);
            }
        });

        GM_registerMenuCommand("清除115登录状态", () => {
            GM_setValue('115_cookies', '');
            GM_setValue('115_last_cookie_refresh', 0);
            showNotification('115状态', '已清除登录状态');
        });

        GM_registerMenuCommand("打开115网盘", () => window.open("https://115.com", "_blank"));

        // 设置定时刷新Cookie
        setInterval(() => {
            const lastRefresh = GM_getValue('115_last_cookie_refresh', 0);
            if (Date.now() - lastRefresh > CONFIG.cookieRefreshInterval) {
                check115Login(false);
            }
        }, 5 * 60 * 1000); // 每5分钟检查一次

        // 监听页面变化
        const observer = new MutationObserver(handleDomChanges);
        observer.observe(document, {
            childList: true,
            subtree: true
        });

        // 初始添加按钮
        addActionButtons();
    }

    // DOM变化处理
    function handleDomChanges(mutations) {
        for (const mutation of mutations) {
            if (mutation.addedNodes.length) {
                addActionButtons();
            }
        }
    }

    // 添加操作按钮
    function addActionButtons() {
        // BT4G网站处理
        if (window.location.host.includes('bt4gprx.com')) {
            handleBT4GSite();
        }

        /* ------------- CLM计划移除Start ------------- */
        // CLM网站处理
        else if (/cl[^.]+\.[^.]+\..+/.test(window.location.host)) {
            handleCLMSite();
        }
        /* ------------- CLM计划移除End ------------- */
        // 其他网站处理
        else {
            handleCommonSites();
        }
        /* ------------- CLM计划移除Start ------------- */
    }
    // 处理CLM网站
    function handleCLMSite() {
        // 处理磁力链部分
        const magnetWrapper = document.querySelector('.Information_magnet_wrapper');
        if (!magnetWrapper || magnetWrapper.dataset.buttonsAdded) return;

        // 标记已处理
        magnetWrapper.dataset.buttonsAdded = true;

        // 获取磁力链接
        const magnetLink = document.querySelector('.Information_magnet');
        if (!magnetLink || !magnetLink.href.startsWith('magnet:')) return;

        // 创建按钮容器
        const btnContainer = document.createElement('div');
        btnContainer.className = 'magnet-action-buttons';
        btnContainer.style.margin = '10px 0';
        btnContainer.style.display = 'flex';
        btnContainer.style.gap = '10px';

        // 添加按钮
        btnContainer.appendChild(createCommonCopyButton(magnetLink));
        btnContainer.appendChild(createCommonOfflineButton(magnetLink));

        // 插入到DOM - 放在磁力链后面
        magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
        /* ------------- CLM计划移除End ------------- */
    }

    // 处理BT4G网站
    function handleBT4GSite() {
        document.querySelectorAll('.card-body').forEach(cardBody => {
            if (cardBody.dataset.buttonsAdded) return;

            const magnetBtn = cardBody.querySelector('a[href*="downloadtorrentfile.com/hash/"]');
            if (!magnetBtn) return;

            // 标记已处理
            cardBody.dataset.buttonsAdded = true;

            // 创建按钮容器
            const btnContainer = document.createElement('div');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-block';
            btnContainer.style.marginRight = '10px';

            // 添加按钮
            btnContainer.appendChild(createBT4GCopyButton(magnetBtn));
            btnContainer.appendChild(createBT4GOfflineButton(magnetBtn));

            // 插入到DOM
            magnetBtn.parentNode.insertBefore(btnContainer, magnetBtn);
        });
    }

    // 创建BT4G风格的复制按钮
    function createBT4GCopyButton(element) {
        const btn = document.createElement('button');
        btn.className = 'btn btn-sm copy-magnet-btn';
        btn.innerHTML = '🔗 复制磁力链';
        Object.assign(btn.style, {
            cursor: 'pointer',
            backgroundColor: '#000000',
            color: '#ffffff',
            border: '1px solid #000000',
            borderRadius: '6px',
            padding: isMobile ? '0.5rem 1rem' : '0.25rem 0.5rem',
            fontSize: isMobile ? '1rem' : '0.875rem',
            marginRight: '10px',
            transition: 'all 0.15s ease-in-out',
            fontWeight: '400',
            lineHeight: isMobile ? '1.2' : '2',
            textAlign: 'center',
            verticalAlign: 'middle',
            userSelect: 'none',
            // 移动端优化
            touchAction: 'manipulation',
            minWidth: isMobile ? '120px' : 'auto',
            minHeight: isMobile ? '40px' : 'auto'
        });

        btn.addEventListener('mouseenter', () => {
            btn.style.backgroundColor = '#333333';
            btn.style.borderColor = '#333333';
        });
        btn.addEventListener('mouseleave', () => {
            btn.style.backgroundColor = '#000000';
            btn.style.borderColor = '#000000';
        });
        // 触摸反馈
        btn.addEventListener('touchstart', () => {
            btn.style.backgroundColor = '#333333';
            btn.style.borderColor = '#333333';
        });
        btn.addEventListener('touchend', () => {
            btn.style.backgroundColor = '#000000';
            btn.style.borderColor = '#000000';
        });

        const handleCopy = async (e) => {
            e.preventDefault();
            e.stopPropagation();

            const magnetLink = await extractMagnetLink(element);
            if (!magnetLink) return;

            try {
                GM_setClipboard(magnetLink, 'text');

                // 移动端备用方案
                if (isMobile && navigator.clipboard && navigator.clipboard.writeText) {
                    try {
                        await navigator.clipboard.writeText(magnetLink);
                    } catch (clipboardError) {
                        console.log('使用navigator.clipboard失败:', clipboardError);
                    }
                }

                showNotification('磁力链已复制', magnetLink);

                // 按钮反馈效果
                const originalHTML = btn.innerHTML;
                btn.innerHTML = '🔗 复制成功!';
                btn.disabled = true;
                setTimeout(() => {
                    btn.innerHTML = originalHTML;
                    btn.disabled = false;
                }, 2000);
            } catch (error) {
                showNotification('复制失败', `请手动复制: ${magnetLink}`);
            }
        };

        // 同时监听点击和触摸事件
        btn.addEventListener('click', handleCopy);
        btn.addEventListener('touchend', handleCopy);

        return btn;
    }

    // 创建BT4G风格的115离线按钮
    function createBT4GOfflineButton(element) {
        const btn = document.createElement('button');
        btn.className = 'btn btn-sm offline-115-btn';
        btn.innerHTML = '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:text-bottom;margin-right:2px;"> 推送到115离线';
        Object.assign(btn.style, {
            cursor: 'pointer',
            backgroundColor: '#1E50A2',
            color: '#fff',
            border: '1px solid #1a4580',
            borderRadius: '6px',
            padding: isMobile ? '0.5rem 1rem' : '0.25rem 0.5rem',
            fontSize: isMobile ? '1rem' : '0.875rem',
            transition: 'all 0.15s ease-in-out',
            fontWeight: '400',
            lineHeight: isMobile ? '1.2' : '2',
            textAlign: 'center',
            verticalAlign: 'middle',
            userSelect: 'none',
            // 移动端优化
            touchAction: 'manipulation',
            minWidth: isMobile ? '140px' : 'auto',
            minHeight: isMobile ? '40px' : 'auto'
        });

        btn.addEventListener('mouseenter', () => {
            btn.style.backgroundColor = '#1a4580';
            btn.style.borderColor = '#163c70';
        });
        btn.addEventListener('mouseleave', () => {
            btn.style.backgroundColor = '#1E50A2';
            btn.style.borderColor = '#1a4580';
        });
        // 触摸反馈
        btn.addEventListener('touchstart', () => {
            btn.style.backgroundColor = '#1a4580';
            btn.style.borderColor = '#163c70';
        });
        btn.addEventListener('touchend', () => {
            btn.style.backgroundColor = '#1E50A2';
            btn.style.borderColor = '#1a4580';
        });

        const handleOffline = async (e) => {
            e.preventDefault();
            e.stopPropagation();

            const magnetLink = await extractMagnetLink(element);
            if (!magnetLink) return;

            await process115Offline(magnetLink);
        };

        // 同时监听点击和触摸事件
        btn.addEventListener('click', handleOffline);
        btn.addEventListener('touchend', handleOffline);

        return btn;
    }

    // 处理其他通用网站
    function handleCommonSites() {
        // SOBT网站处理
        if (/sobt[^.]+\..+/.test(window.location.host)) {
            handleSOBTSite();
        }
        // BTDig网站处理
        else if (window.location.host.endsWith('btdig.com')) {
            handleBTDigSite();
        }
        // BTMulu网站处理
        else if (window.location.host.endsWith('btmulu.work')) {
            handleBTMuluSite();
        }
        // Nyaa网站处理
        else if (window.location.host.includes('nyaa.si')) {
            handleNyaaSite();
        }
        // DMHY网站处理
        else if (window.location.host.includes('dmhy.org')) {
            handleDMHYSite();
        }
        // BTSOW网站处理
        else if (window.location.host.includes('btsow.pics') || window.location.host.includes('btsow.com')) {
            handleBTSOWSite();
        }
    }

    // 处理BTSOW网站
    function handleBTSOWSite() {
        // 处理搜索结果项
        document.querySelectorAll('.data-list .row > a[href*="/magnet/detail/hash/"]').forEach(titleLink => {
            if (titleLink.dataset.buttonsAdded) return;

            // 标记已处理
            titleLink.dataset.buttonsAdded = true;

            // 创建按钮容器
            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-block';
            btnContainer.style.marginLeft = '15px';
            btnContainer.style.marginRight = '10px';

            // 提取hash
            const hashMatch = titleLink.href.match(/\/hash\/([a-f0-9]+)/i);
            if (!hashMatch || !hashMatch[1]) return;

            const magnetLink = `magnet:?xt=urn:btih:${hashMatch[1]}`;

            // 添加按钮
            btnContainer.appendChild(createCommonCopyButton(magnetLink));
            btnContainer.appendChild(createCommonOfflineButton(magnetLink));

            // 插入到DOM - 放在标题下
            titleLink.parentNode.insertBefore(btnContainer, titleLink.nextSibling);
            titleLink.closest('.row').appendChild(btnContainer);
        });
    }

    // 处理SOBT网站
    function handleSOBTSite() {
        // 处理搜索结果页
        document.querySelectorAll('h3 > a[href^="/torrent/"]').forEach(titleLink => {
            if (titleLink.dataset.buttonsAdded) return;

            // 标记已处理
            titleLink.dataset.buttonsAdded = true;

            // 创建按钮容器
            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-block';
            btnContainer.style.marginLeft = '10px';

            // 添加按钮
            btnContainer.appendChild(createCommonCopyButton(titleLink));
            btnContainer.appendChild(createCommonOfflineButton(titleLink));

            // 插入到DOM
            titleLink.parentNode.insertBefore(btnContainer, titleLink.nextSibling);
        });

        // 处理详情页
        document.querySelectorAll('.panel-body a[href^="magnet:"]').forEach(magnetLink => {
            if (magnetLink.dataset.buttonsAdded) return;

            // 标记已处理
            magnetLink.dataset.buttonsAdded = true;

            // 创建按钮容器
            const btnContainer = document.createElement('div');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.margin = '10px 0';

            // 添加按钮
            btnContainer.appendChild(createCommonCopyButton(magnetLink));
            btnContainer.appendChild(createCommonOfflineButton(magnetLink));

            // 插入到DOM
            magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
        });
    }

    // 处理BTDig网站
    function handleBTDigSite() {
        // 处理搜索结果项
        document.querySelectorAll('.torrent_name > a').forEach(titleLink => {
            if (titleLink.dataset.buttonsAdded) return;

            // 标记已处理
            titleLink.dataset.buttonsAdded = true;

            // 创建按钮容器
            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-block';
            btnContainer.style.marginRight = '10px';

            // 获取磁力链
            const magnetLink = document.querySelector('.torrent_magnet a[href^="magnet:"]');
            if (!magnetLink) return;

            // 添加按钮
            btnContainer.appendChild(createCommonCopyButton(magnetLink));
            btnContainer.appendChild(createCommonOfflineButton(magnetLink));

            // 插入到DOM - 放在标题前面
            titleLink.parentNode.insertBefore(btnContainer, titleLink);
        });
    }

    // 处理BTMulu网站
    function handleBTMuluSite() {
        // 处理搜索结果项
        document.querySelectorAll('article.item a[href^="/hash/"]').forEach(titleLink => {
            if (titleLink.dataset.buttonsAdded) return;

            // 标记已处理
            titleLink.dataset.buttonsAdded = true;

            // 创建按钮容器
            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-block';
            btnContainer.style.marginLeft = '10px';

            // 提取hash
            const hashMatch = titleLink.href.match(/\/hash\/([a-f0-9]+)\.html$/i);
            if (!hashMatch || !hashMatch[1]) return;

            const magnetLink = `magnet:?xt=urn:btih:${hashMatch[1]}`;

            // 添加按钮
            btnContainer.appendChild(createCommonCopyButton(magnetLink));
            btnContainer.appendChild(createCommonOfflineButton(magnetLink));

            // 插入到DOM - 放在标题后面
            titleLink.parentNode.insertBefore(btnContainer, titleLink.nextSibling);
        });
    }

    // 处理Nyaa网站
    function handleNyaaSite() {
        // 处理搜索结果项
        document.querySelectorAll('td.text-center a[href^="magnet:"]').forEach(magnetLink => {
            if (magnetLink.dataset.buttonsAdded) return;

            // 标记已处理
            magnetLink.dataset.buttonsAdded = true;

            // 创建按钮容器
            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-block';
            btnContainer.style.marginLeft = '10px';

            // 添加按钮
            btnContainer.appendChild(createCommonCopyButton(magnetLink));
            btnContainer.appendChild(createCommonOfflineButton(magnetLink));

            // 插入到DOM - 放在磁力链图标后面
            magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
        });
    }

    // 处理DMHY网站
    function handleDMHYSite() {
        // 调整磁力链列宽度为18%
        const magnetHeader = document.querySelector('#topic_list th:nth-child(4)');
        if (magnetHeader) {
            magnetHeader.style.width = '18%';
        }

        // 处理搜索结果项
        document.querySelectorAll('a.download-arrow.arrow-magnet').forEach(magnetLink => {
            if (magnetLink.dataset.buttonsAdded) return;

            // 标记已处理
            magnetLink.dataset.buttonsAdded = true;

            // 创建按钮容器
            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-block';
            btnContainer.style.marginLeft = '5px';

            // 添加按钮
            btnContainer.appendChild(createCommonCopyButton(magnetLink));
            btnContainer.appendChild(createCommonOfflineButton(magnetLink));

            // 插入到DOM - 放在磁力链图标前面
            magnetLink.parentNode.insertBefore(btnContainer, magnetLink);
        });

        // 处理详情页的磁力链接
        document.querySelectorAll('#tabs-1 a.magnet, #tabs-1 a#magnet2').forEach(magnetLink => {
            if (magnetLink.dataset.buttonsAdded) return;

            // 标记已处理
            magnetLink.dataset.buttonsAdded = true;

            // 创建按钮容器
            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-block';
            btnContainer.style.marginLeft = '5px';

            // 添加按钮
            btnContainer.appendChild(createCommonCopyButton(magnetLink));
            btnContainer.appendChild(createCommonOfflineButton(magnetLink));

            // 插入到DOM - 放在磁力链后面
            magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
        });
    }

    // 创建通用复制按钮
    function createCommonCopyButton(element) {
        const btn = document.createElement('button');
        btn.className = 'btn btn-info me-2 copy-magnet-btn';
        btn.innerHTML = '🔗 复制';
        Object.assign(btn.style, {
            cursor: 'pointer',
            backgroundColor: '#000000',
            color: '#ffffff',
            border: '1px solid #000000',
            borderRadius: '4px',
            padding: isMobile ? '8px 12px' : '4px 8px',
            fontSize: isMobile ? '14px' : '12px',
            marginRight: '5px',
            transition: 'all 0.15s ease-in-out',
            fontWeight: '400',
            lineHeight: '1.5',
            verticalAlign: 'middle',
            // 移动端优化
            touchAction: 'manipulation',
            minWidth: isMobile ? '120px' : 'auto',
            minHeight: isMobile ? '40px' : 'auto'
        });

        btn.addEventListener('mouseenter', () => {
            btn.style.backgroundColor = '#333333';
            btn.style.borderColor = '#333333';
        });
        btn.addEventListener('mouseleave', () => {
            btn.style.backgroundColor = '#000000';
            btn.style.borderColor = '#000000';
        });
        // 触摸反馈
        btn.addEventListener('touchstart', () => {
            btn.style.backgroundColor = '#333333';
            btn.style.borderColor = '#333333';
        });
        btn.addEventListener('touchend', () => {
            btn.style.backgroundColor = '#000000';
            btn.style.borderColor = '#000000';
        });

        const handleCopy = async (e) => {
            e.preventDefault();
            e.stopPropagation();

            const magnetLink = await extractMagnetLink(element);
            if (!magnetLink) return;

            try {
                GM_setClipboard(magnetLink, 'text');

                // 移动端备用方案
                if (isMobile && navigator.clipboard && navigator.clipboard.writeText) {
                    try {
                        await navigator.clipboard.writeText(magnetLink);
                    } catch (clipboardError) {
                        console.log('使用navigator.clipboard失败:', clipboardError);
                    }
                }

                showNotification('磁力链已复制', magnetLink);

                // 按钮反馈效果
                const originalHTML = btn.innerHTML;
                btn.innerHTML = '🔗 完成';
                btn.disabled = true;
                setTimeout(() => {
                    btn.innerHTML = originalHTML;
                    btn.disabled = false;
                }, 2000);
            } catch (error) {
                showNotification('复制失败', `请手动复制: ${magnetLink}`);
            }
        };

        // 同时监听点击和触摸事件
        btn.addEventListener('click', handleCopy);
        btn.addEventListener('touchend', handleCopy);

        return btn;
    }

    // 创建通用115离线按钮
    function createCommonOfflineButton(element) {
        const btn = document.createElement('button');
        btn.className = 'btn btn-danger me-2 offline-115-btn';
        btn.innerHTML = '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:middle;"> 115离线';
        Object.assign(btn.style, {
            cursor: 'pointer',
            backgroundColor: '#1E50A2',
            color: '#fff',
            border: '1px solid #1a4580',
            borderRadius: '4px',
            padding: isMobile ? '8px 12px' : '4px 8px',
            fontSize: isMobile ? '14px' : '12px',
            transition: 'all 0.15s ease-in-out',
            fontWeight: '400',
            lineHeight: '1.5',
            verticalAlign: 'middle',
            // 移动端优化
            touchAction: 'manipulation',
            minWidth: isMobile ? '140px' : 'auto',
            minHeight: isMobile ? '40px' : 'auto'
        });

        btn.addEventListener('mouseenter', () => {
            btn.style.backgroundColor = '#1a4580';
            btn.style.borderColor = '#163c70';
        });
        btn.addEventListener('mouseleave', () => {
            btn.style.backgroundColor = '#1E50A2';
            btn.style.borderColor = '#1a4580';
        });
        // 触摸反馈
        btn.addEventListener('touchstart', () => {
            btn.style.backgroundColor = '#1a4580';
            btn.style.borderColor = '#163c70';
        });
        btn.addEventListener('touchend', () => {
            btn.style.backgroundColor = '#1E50A2';
            btn.style.borderColor = '#1a4580';
        });

        const handleOffline = async (e) => {
            e.preventDefault();
            e.stopPropagation();

            const magnetLink = await extractMagnetLink(element);
            if (!magnetLink) return;

            await process115Offline(magnetLink);
        };

        // 同时监听点击和触摸事件
        btn.addEventListener('click', handleOffline);
        btn.addEventListener('touchend', handleOffline);

        return btn;
    }

    // 从元素提取磁力链
    async function extractMagnetLink(element) {
        try {
            // 如果是SOBT网站的标题链
            if (element.href && element.href.includes('/torrent/')) {
                const hashMatch = element.href.match(/\/torrent\/([a-f0-9]+)\.html$/i);
                if (hashMatch && hashMatch[1]) {
                    return `magnet:?xt=urn:btih:${hashMatch[1]}`;
                }
            }

            // 如果是BT4G网站的磁力链
            else if (element.href && element.href.includes('downloadtorrentfile.com/hash/')) {
                const hashMatch = element.href.match(/hash\/([a-f0-9]+)/i);
                if (hashMatch && hashMatch[1]) {
                    return `magnet:?xt=urn:btih:${hashMatch[1]}`;
                }
            }

            // 如果是BTMulu网站的标题链
            else if (element.href && element.href.includes('/hash/')) {
                const hashMatch = element.href.match(/\/hash\/([a-f0-9]+)\.html$/i);
                if (hashMatch && hashMatch[1]) {
                    return `magnet:?xt=urn:btih:${hashMatch[1]}`;
                }
            }
            // 如果是BTSOW网站的标题链
            else if (element.href && element.href.includes('/magnet/detail/hash/')) {
                const hashMatch = element.href.match(/\/hash\/([a-f0-9]+)/i);
                if (hashMatch && hashMatch[1]) {
                    return `magnet:?xt=urn:btih:${hashMatch[1]}`;
                }
            }
            // 如果是Nyaa/DMHY网站或直接磁力链
            else if (element.href && element.href.startsWith('magnet:')) {
                return element.href;
            }

            // 如果是字符串直接返回
            else if (typeof element === 'string' && element.startsWith('magnet:')) {
                return element;
            }

            throw new Error('无法提取磁力链Hash');
        } catch (error) {
            showNotification('错误', error.message);
            return null;
        }
    }

    // 检查115登录状态
    async function check115Login(forceCheck = false) {
        try {
            // 如果有强制检查标志或者Cookie为空或者超过刷新间隔,则重新获取
            const lastRefresh = GM_getValue('115_last_cookie_refresh', 0);
            const currentCookies = GM_getValue('115_cookies', '');

            if (!forceCheck && currentCookies && Date.now() - lastRefresh < CONFIG.cookieRefreshInterval) {
                return true;
            }

            // 尝试获取当前有效的Cookie
            const cookies = await getCurrent115Cookies();
            if (!cookies) {
                GM_setValue('115_cookies', '');
                GM_setValue('115_last_cookie_refresh', 0);
                return false;
            }

            // 验证Cookie是否有效
            const isValid = await validate115Cookies(cookies);
            if (isValid) {
                GM_setValue('115_cookies', cookies);
                GM_setValue('115_last_cookie_refresh', Date.now());
                return true;
            }

            GM_setValue('115_cookies', '');
            GM_setValue('115_last_cookie_refresh', 0);
            return false;
        } catch (error) {
            console.error('检查登录状态失败:', error);
            return false;
        }
    }

    // 获取当前有效的115 Cookie
    function getCurrent115Cookies() {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                url: 'https://115.com/',
                method: 'GET',
                anonymous: true, // 不发送现有Cookie
                onload: function(response) {
                    // 从响应头获取Cookie
                    const cookieHeader = response.responseHeaders
                        .split('\n')
                        .find(row => row.toLowerCase().startsWith('set-cookie:'));

                    if (cookieHeader) {
                        const cookies = cookieHeader.replace(/^set-cookie:\s*/i, '').split(';')[0];
                        resolve(cookies);
                    } else {
                        // 检查是否重定向到登录页面
                        if (response.finalUrl.includes('login.115.com')) {
                            resolve('');
                        } else {
                            // 尝试使用之前存储的Cookie
                            const savedCookies = GM_getValue('115_cookies', '');
                            resolve(savedCookies);
                        }
                    }
                },
                onerror: () => resolve('')
            });
        });
    }

    // 验证Cookie是否有效
    function validate115Cookies(cookies) {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                url: 'https://115.com/web/lixian/',
                method: 'GET',
                headers: {
                    'Cookie': cookies
                },
                onload: function(response) {
                    // 检查是否重定向到登录页面
                    if (response.finalUrl.includes('login.115.com')) {
                        resolve(false);
                    } else {
                        resolve(true);
                    }
                },
                onerror: () => resolve(false)
            });
        });
    }

    // 115离线下载处理流程
    async function process115Offline(magnetLink) {
        const notificationId = Date.now();

        try {
            // 步骤1:检查登录状态
            showNotification('115离线', '正在检查登录状态...', notificationId);
            const isLoggedIn = await check115Login(true);
            if (!isLoggedIn) {
                throw new Error('请先登录115网盘');
            }

            // 步骤2:提交离线任务
            showNotification('115离线', '正在提交离线任务...', notificationId);
            const result = await submit115OfflineTask(magnetLink);

            // 处理结果
            handleOfflineResult(result);

        } catch (error) {
            showNotification('115离线失败', error.message);

            // 如果是未登录错误,显示登录提示
            if (error.message.includes('登录')) {
                setTimeout(() => {
                    if (confirm('需要登录115网盘,是否进入115网盘登录页面?')) {
                        window.open('https://115.com/?mode=login', '_blank');
                    }
                }, 500);
            }

        } finally {
            // 关闭进度通知
            GM_notification({ id: notificationId, done: true });
        }
    }

    // 提交115离线任务
    async function submit115OfflineTask(magnetLink) {
        const cookies = GM_getValue('115_cookies', '');
        if (!cookies) {
            throw new Error('未检测到有效的登录状态');
        }

        const response = await fetch115Api(
            `https://115.com/web/lixian/?ct=lixian&ac=add_task_url&url=${encodeURIComponent(magnetLink)}`,
            {
                headers: {
                    'Cookie': cookies
                }
            }
        );

        return tryParseJson(response);
    }

    // 处理离线结果
    function handleOfflineResult(result) {
        if (!result) {
            throw new Error('无效的响应');
        }

        if (result.state) {
            showNotification('115离线成功', '任务已成功添加到离线下载列表');
            return;
        }

        const errorMsg = ERROR_CODES[result.errcode] || result.error_msg || '未知错误';
        throw new Error(errorMsg);
    }

    // 通用请求函数
    function fetch115Api(url, options = {}) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                url: url,
                method: options.method || 'GET',
                headers: {
                    'User-Agent': navigator.userAgent,
                    'Origin': 'https://115.com',
                    ...(options.headers || {})
                },
                data: options.body,
                onload: function(response) {
                    if (response.status >= 200 && response.status < 300) {
                        resolve(response.responseText);
                    } else {
                        reject(new Error(`请求失败: ${response.status}`));
                    }
                },
                onerror: reject
            });
        });
    }

    // 尝试解析JSON
    function tryParseJson(text) {
        try {
            return JSON.parse(text);
        } catch (e) {
            return null;
        }
    }

    // 显示通知
    function showNotification(title, text, id = null) {
        GM_notification({
            title: title,
            text: text,
            timeout: CONFIG.notificationTimeout,
            ...(id ? { id } : {})
        });
    }
})();