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

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

// ==UserScript==
// @name         一键复制磁力链和推送到115离线
// @author       [email protected]
// @description  目前支持BT4G/BTDig/SOBT/BTMulu/Nyaa/DMHY/CLM(磁力链搜索引擎)添加一键复制磁力链和推送到115网盘进行离线(推送离线任务需当前浏览器已登录115会员账号)
// @version      1.1.1.20250727
// @icon         https://github.githubassets.com/assets/mona-loading-default-c3c7aad1282f.gif
// @include      *://bt4gprx.com/*
// @include      *://btdig.com/*
// @include      *://*.btdig.com/*
// @include      *://sobt*.*/*
// @include      *://nyaa.si/*
// @include      *://cl*.cl*/*
// @include      *://*.btmulu.*/*
// @include      *://btmulu.*/*
// @include      *://idope.se/*
// @include      *://*.dmhy.org/*
// @include      *://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: '未知错误'
    };

    // 按钮样式常量
    const BUTTON_STYLES = {
        common: {
            cursor: 'pointer',
            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 ? '120px' : 'auto',
            minHeight: isMobile ? '40px' : 'auto'
        },
        copy: {
            backgroundColor: '#000000',
            color: '#ffffff',
            border: '1px solid #000000',
            marginRight: '5px'
        },
        offline: {
            backgroundColor: '#1E50A2',
            color: '#fff',
            border: '1px solid #1a4580'
        },
        hoverCopy: {
            backgroundColor: '#333333',
            borderColor: '#333333'
        },
        hoverOffline: {
            backgroundColor: '#1a4580',
            borderColor: '#163c70'
        }
    };

    // 主初始化函数
    function initializeScript() {
        // 添加Tampermonkey菜单命令
        addMenuCommands();
        
        // 设置定时刷新Cookie
        setInterval(checkCookieRefresh, 5 * 60 * 1000);
        
        // 监听页面变化
        setupMutationObserver();
        
        // 初始添加按钮
        addActionButtons();
        injectBtmuluTitleWidthCSS();
        insertIdopeDetailButtons();
    }

    // 添加Tampermonkey菜单命令
    function addMenuCommands() {
        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是否需要刷新
    function checkCookieRefresh() {
        const lastRefresh = GM_getValue('115_last_cookie_refresh', 0);
        if (Date.now() - lastRefresh > CONFIG.cookieRefreshInterval) {
            check115Login(false);
        }
    }

    // 设置DOM变化观察器
    function setupMutationObserver() {
        const observer = new MutationObserver(handleDomChanges);
        observer.observe(document, {
            childList: true,
            subtree: true
        });
    }

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

    // 注入btmulu标题宽度自适应CSS
    function injectBtmuluTitleWidthCSS() {
        if (!/([^.]+\.)?btmulu\.[^/]+$/.test(window.location.host)) return;
        if (document.getElementById('btmulu-title-width-style')) return;
        
        const style = document.createElement('style');
        style.id = 'btmulu-title-width-style';
        style.textContent = `
            article.item > div > a {
                display: inline-block !important;
                width: auto !important;
                max-width: 100% !important;
                vertical-align: middle;
            }
            article.item > div > a h4 {
                display: inline !important;
            }
        `;
        document.head.appendChild(style);
    }

    // 处理iDope详情页按钮插入到标题左侧
    function insertIdopeDetailButtons() {
        const nameDiv = document.getElementById('name');
        const magnetA = document.getElementById('mangetinfo');
        if (!nameDiv || !magnetA) return;
        if (nameDiv.dataset.buttonsAdded) return;
        
        nameDiv.dataset.buttonsAdded = 'true';
        const magnetLink = magnetA.href;
        
        const btnContainer = document.createElement('span');
        btnContainer.className = 'magnet-action-buttons';
        btnContainer.style.display = 'inline-flex';
        btnContainer.style.alignItems = 'center';
        btnContainer.style.marginRight = '10px';
        
        btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope'));
        btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope'));
        
        nameDiv.insertBefore(btnContainer, nameDiv.firstChild);
    }

    // 添加操作按钮
    function addActionButtons() {
        // BT4G网站处理
        if (window.location.host.includes('bt4gprx.com')) {
            handleBT4GSite();
        }
        // CLM网站处理
        else if (/cl[^.]+\.[^.]+\..+/.test(window.location.host)) {
            handleCLMSite();
        }
        // iDope网站处理
        else if (window.location.host.includes('idope.se')) {
            handleIdopeSite();
        }
        // 其他网站处理
        else {
            handleCommonSites();
        }
    }
    
    // 处理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(createButton('copy', magnetLink));
        btnContainer.appendChild(createButton('offline', magnetLink));

        magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
    }

    // 处理iDope网站
    function handleIdopeSite() {
        document.querySelectorAll('.resultdivtop').forEach(resultDiv => {
            if (resultDiv.dataset.buttonsAdded) return;

            resultDiv.dataset.buttonsAdded = true;
            const titleLink = resultDiv.querySelector('a[href^="/torrent/"]');
            if (!titleLink) return;

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

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

            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-flex';
            btnContainer.style.alignItems = 'center';
            btnContainer.style.marginRight = '8px';

            btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope'));
            btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope'));

            const titleDiv = titleLink.querySelector('.resultdivtopname');
            if (titleDiv) {
                titleDiv.insertBefore(btnContainer, titleDiv.firstChild);
            } else {
                titleLink.insertBefore(btnContainer, titleLink.firstChild);
            }
        });
    }

    // 创建按钮的通用函数
    function createButton(type, element, icon = null, styleType = 'common', noDefaultClick = false) {
        const btn = document.createElement('button');
        btn.className = `${type}-magnet-btn`;
        
        // 设置按钮内容和样式
        if (styleType === 'idope') {
            applyIdopeButtonStyle(btn, type, icon);
        } else if (styleType === 'btmulu') {
            applyBtmuluButtonStyle(btn, type, element);
        } else {
            applyCommonButtonStyle(btn, type, icon);
        }

        // 添加点击事件
        if (!noDefaultClick) {
            setupButtonClickHandler(btn, type, element, styleType);
        }
        
        return btn;
    }

    // 应用iDope风格按钮样式
    function applyIdopeButtonStyle(btn, type, icon) {
        Object.assign(btn.style, {
            cursor: 'pointer',
            backgroundColor: 'transparent',
            color: '#555',
            border: '1px solid #ddd',
            borderRadius: '4px',
            padding: '2px 6px',
            fontSize: '12px',
            marginRight: '5px',
            transition: 'all 0.15s ease-in-out',
            fontWeight: '400',
            lineHeight: '1.5',
            verticalAlign: 'middle',
            touchAction: 'manipulation',
            width: '30px',
            height: '26px',
            minWidth: '30px',
            minHeight: '26px',
            boxSizing: 'border-box',
        });

        btn.innerHTML = icon || (type === 'copy' ? '🔗' : '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">');

        btn.addEventListener('mouseenter', () => {
            btn.style.backgroundColor = '#f0f0f0';
            btn.style.borderColor = '#ccc';
        });
        btn.addEventListener('mouseleave', () => {
            btn.style.backgroundColor = 'transparent';
            btn.style.borderColor = '#ddd';
        });
        btn.addEventListener('touchstart', () => {
            btn.style.backgroundColor = '#f0f0f0';
            btn.style.borderColor = '#ccc';
        });
        btn.addEventListener('touchend', () => {
            btn.style.backgroundColor = 'transparent';
            btn.style.borderColor = '#ddd';
        });
    }

    // 应用BTMulu风格按钮样式
    function applyBtmuluButtonStyle(btn, type, typeLabel) {
        btn.textContent = type === 'copy' ? '复制' : '离线';
        btn.style.height = typeLabel.offsetHeight ? typeLabel.offsetHeight + 'px' : '22px';
        btn.style.lineHeight = typeLabel.offsetHeight ? typeLabel.offsetHeight + 'px' : '22px';
        btn.style.padding = '0 10px';
        btn.style.fontSize = '12px';
        btn.style.background = type === 'copy' ? '#000' : '#1E50A2';
        btn.style.color = '#fff';
        btn.style.border = 'none';
        btn.style.borderRadius = '4px';
        btn.style.marginRight = '5px';
        btn.style.marginTop = '1px';
        btn.style.marginLeft = type === 'copy' ? '-2px' : '2px';
        btn.style.verticalAlign = 'middle';
        btn.style.cursor = 'pointer';
        btn.style.transition = 'background 0.15s';
        
        btn.addEventListener('mouseenter', () => {
            btn.style.background = type === 'copy' ? '#333' : '#163c70';
        });
        btn.addEventListener('mouseleave', () => {
            btn.style.background = type === 'copy' ? '#000' : '#1E50A2';
        });
    }

    // 应用通用按钮样式
    function applyCommonButtonStyle(btn, type, icon) {
        Object.assign(btn.style, BUTTON_STYLES.common);
        Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.copy : BUTTON_STYLES.offline);
        
        btn.innerHTML = icon || (type === 'copy' ? '🔗 复制' : '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:middle;"> 离线');

        btn.addEventListener('mouseenter', () => {
            Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.hoverCopy : BUTTON_STYLES.hoverOffline);
        });
        btn.addEventListener('mouseleave', () => {
            Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.copy : BUTTON_STYLES.offline);
        });
        btn.addEventListener('touchstart', () => {
            Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.hoverCopy : BUTTON_STYLES.hoverOffline);
        });
        btn.addEventListener('touchend', () => {
            Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.copy : BUTTON_STYLES.offline);
        });
    }

    // 设置按钮点击事件处理
    function setupButtonClickHandler(btn, type, element, styleType) {
        const handleClick = async (e) => {
            e.preventDefault();
            e.stopPropagation();

            const magnetLink = typeof element === 'string' ? element : await extractMagnetLink(element);
            if (!magnetLink) return;

            if (type === 'copy') {
                // 针对不同站点传递 styleType
                let feedbackType = styleType;
                // nyaa.si 识别
                if (window.location.host.includes('nyaa.si')) feedbackType = 'nyaa';
                await handleCopyAction(btn, magnetLink, feedbackType);
            } else {
                await handleOfflineAction(btn, magnetLink, styleType);
            }
        };
        
        btn.addEventListener('click', handleClick);
        btn.addEventListener('touchend', handleClick);
    }

    // 处理复制操作
    async function handleCopyAction(btn, magnetLink, styleType) {
        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;
            let feedbackHTML = '';
            if (["idope", "btmulu", "nyaa"].includes(styleType)) {
                // 绿色对勾SVG
                feedbackHTML = '<svg width="18" height="18" viewBox="0 0 18 18" style="display:inline-block;vertical-align:middle;"><circle cx="9" cy="9" r="9" fill="#4caf50"/><polyline points="5,10 8,13 13,6" fill="none" stroke="#fff" stroke-width="2"/></svg>';
            } else {
                // "完成"或"成功"
                feedbackHTML = styleType === 'bt4g' ? '🔗 完成' : '🔗 完成';
            }
            btn.innerHTML = feedbackHTML;
            btn.disabled = true;
            setTimeout(() => {
                btn.innerHTML = originalHTML;
                btn.disabled = false;
            }, 2000);
        } catch (error) {
            showNotification('复制失败', `请手动复制: ${magnetLink}`);
        }
    }

    // 处理离线操作
    async function handleOfflineAction(btn, magnetLink, styleType) {
        await process115Offline(magnetLink);
        // 按钮反馈效果
        const originalHTML = btn.innerHTML;
        btn.innerHTML = styleType === 'idope' ? '<svg width="18" height="18" viewBox="0 0 18 18" style="display:inline-block;vertical-align:middle;"><circle cx="9" cy="9" r="9" fill="#4caf50"/><polyline points="5,10 8,13 13,6" fill="none" stroke="#fff" stroke-width="2"/></svg>' : 
                      styleType === 'btmulu' ? '成功' : 
                      '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:middle;"> 成功';
        btn.disabled = true;
        setTimeout(() => {
            btn.innerHTML = originalHTML;
            btn.disabled = false;
        }, 2000);
    }

    // 处理BT4G网站
    function handleBT4GSite() {
        // 在搜索结果标题前插入按钮
        document.querySelectorAll('.result-item h5 > a[href^="/magnet/"]').forEach(titleA => {
            if (titleA.dataset.bt4gButtonsAdded) return;
            titleA.dataset.bt4gButtonsAdded = 'true';

            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-block';
            btnContainer.style.marginRight = '8px';
            btnContainer.style.verticalAlign = 'middle';

            // 复制按钮
            const copyBtn = createButton('copy', titleA, null, 'common', true);
            copyBtn.addEventListener('click', async (e) => {
                e.preventDefault();
                e.stopPropagation();
                const magnet = await fetchBT4GMagnetFromDetail(titleA.href);
                if (!magnet) return showNotification('复制失败', '未获取到磁力链');
                GM_setClipboard(magnet, 'text');
                showNotification('磁力链已复制', magnet);
                copyBtn.innerHTML = '🔗 完成';
                copyBtn.disabled = true;
                setTimeout(() => {
                    copyBtn.innerHTML = '🔗 复制';
                    copyBtn.disabled = false;
                }, 2000);
            });

            // 离线按钮
            const offlineBtn = createButton('offline', titleA, null, 'common', true);
            offlineBtn.addEventListener('click', async (e) => {
                e.preventDefault();
                e.stopPropagation();
                const magnet = await fetchBT4GMagnetFromDetail(titleA.href);
                if (!magnet) return showNotification('推送失败', '未获取到磁力链');
                await process115Offline(magnet);
                offlineBtn.innerHTML = '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:text-bottom;margin-right:2px;"> 成功';
                offlineBtn.disabled = true;
                setTimeout(() => {
                    offlineBtn.innerHTML = '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:text-bottom;margin-right:2px;"> 离线';
                    offlineBtn.disabled = false;
                }, 2000);
            });

            btnContainer.appendChild(copyBtn);
            btnContainer.appendChild(offlineBtn);
            titleA.parentNode.insertBefore(btnContainer, titleA);
        });

        // 详情页按钮逻辑
        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(createButton('copy', magnetBtn, '🔗 复制', 'bt4g'));
            btnContainer.appendChild(createButton('offline', magnetBtn, '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:text-bottom;margin-right:2px;"> 离线', 'bt4g'));

            magnetBtn.parentNode.insertBefore(btnContainer, magnetBtn);
        });
    }

    // 异步获取BT4G详情页磁力链
    async function fetchBT4GMagnetFromDetail(detailHref) {
        try {
            let url = detailHref;
            if (!/^https?:/.test(url)) {
                url = location.origin + url;
            }
            const resp = await fetch(url, { credentials: 'omit' });
            const html = await resp.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            const magnetA = doc.querySelector('a.btn.btn-primary.me-2[href*="downloadtorrentfile.com/hash/"]');
            if (!magnetA) return null;
            const hashMatch = magnetA.href.match(/hash\/([a-f0-9]{40})/i);
            if (!hashMatch) return null;
            return `magnet:?xt=urn:btih:${hashMatch[1]}`;
        } catch (e) {
            return null;
        }
    }

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

    // 处理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.style.marginRight = '5px';

            btnContainer.appendChild(createButton('copy', titleLink));
            btnContainer.appendChild(createButton('offline', titleLink));

            titleLink.parentNode.insertBefore(btnContainer, titleLink);
        });

        // 处理详情页的磁力链接
        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(createButton('copy', magnetLink));
            btnContainer.appendChild(createButton('offline', magnetLink));

            magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
        });

        // 处理详情页磁力链 input 框右侧按钮
        const mLinkInput = document.getElementById('m_link');
        if (mLinkInput && !mLinkInput.dataset.idopeButtonsAdded) {
            mLinkInput.dataset.idopeButtonsAdded = 'true';
            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-flex';
            btnContainer.style.alignItems = 'center';
            btnContainer.style.marginLeft = '8px';

            // 复制按钮
            btnContainer.appendChild(createButton('copy', mLinkInput.value, '🔗', 'idope'));
            // 离线按钮
            btnContainer.appendChild(createButton('offline', mLinkInput.value, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope'));

            mLinkInput.parentNode.insertBefore(btnContainer, mLinkInput.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';

            let resultDiv = titleLink.closest('.one_result');
            let magnetLink = resultDiv ? resultDiv.querySelector('.torrent_magnet a[href^="magnet:"]') : null;
            if (!magnetLink) return;

            btnContainer.appendChild(createButton('copy', magnetLink));
            btnContainer.appendChild(createButton('offline', magnetLink));

            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 h4 = titleLink.querySelector('h4');
            if (!h4) return;
            const typeLabel = h4.querySelector('span.label');
            if (!typeLabel) return;

            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-flex';
            btnContainer.style.alignItems = 'center';
            btnContainer.style.marginLeft = '10px';
            btnContainer.style.gap = '5px';

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

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

            // 使用iDope风格的按钮
            btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope'));
            btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope'));

            typeLabel.after(btnContainer);
        });
    }

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

            magnetLink.dataset.buttonsAdded = true;

            let tr = magnetLink.closest('tr');
            let downloadBtn = tr ? tr.querySelector("a[href^='/download/']") : null;
            const btnContainer = document.createElement('span');
            btnContainer.className = 'magnet-action-buttons';
            btnContainer.style.display = 'inline-flex';
            btnContainer.style.alignItems = 'center';
            btnContainer.style.marginRight = '6px';

            btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope'));
            btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope'));

            if (downloadBtn) {
                downloadBtn.parentNode.insertBefore(btnContainer, downloadBtn);
            } else {
                magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
            }
        });
    }

    // 处理DMHY网站
    function handleDMHYSite() {
        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(createButton('copy', magnetLink));
            btnContainer.appendChild(createButton('offline', magnetLink));

            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(createButton('copy', magnetLink));
            btnContainer.appendChild(createButton('offline', magnetLink));

            magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
        });
    }

    // 从元素提取磁力链
    async function extractMagnetLink(element) {
        try {
            if (element.href) {
                // SOBT网站的标题链
                if (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.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.includes('/hash/')) {
                    const hashMatch = element.href.match(/\/hash\/([a-f0-9]+)\.html$/i);
                    if (hashMatch && hashMatch[1]) {
                        return `magnet:?xt=urn:btih:${hashMatch[1]}`;
                    }
                }
                // iDope网站的标题链
                else if (element.href.includes('/torrent/')) {
                    const hashMatch = element.href.match(/\/torrent\/[^\/]+\/([a-f0-9]+)\//i);
                    if (hashMatch && hashMatch[1]) {
                        return `magnet:?xt=urn:btih:${hashMatch[1]}`;
                    }
                }
                // Nyaa/DMHY网站或直接磁力链
                else if (element.href.startsWith('magnet:')) {
                    return element.href;
                }
            }

            // 如果是字符串直接返回
            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 {
            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;
            }

            const cookies = await getCurrent115Cookies();
            if (!cookies) {
                GM_setValue('115_cookies', '');
                GM_setValue('115_last_cookie_refresh', 0);
                return false;
            }

            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,
                onload: function(response) {
                    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 {
                            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) {
                    resolve(!response.finalUrl.includes('login.115.com'));
                },
                onerror: () => resolve(false)
            });
        });
    }

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

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

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

    // 提交离线任务
    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 } : {})
        });
    }

    // 启动脚本
    initializeScript();
})();