Greasy Fork 支持简体中文。

阿里云盘分享链接批量打开

从粘贴的文本中提取阿里云盘分享链接和提取码,并提供批量打开及自动输入提取码功能。

// ==UserScript==
// @name         阿里云盘分享链接批量打开
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  从粘贴的文本中提取阿里云盘分享链接和提取码,并提供批量打开及自动输入提取码功能。
// @author       Gogo & Claude 3.5 Sonnet
// @match        https://www.aliyundrive.com/*
// @match        https://www.alipan.com/*
// @grant        GM_setClipboard
// @grant        GM_openInTab
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

/*
 * 阿里云盘分享链接批量打开脚本
 * 版本: 1.0
 * 作者: Gogo & Claude 3.5 Sonnet
 * 创建日期: 2024-09-28
 * 最后修改: 2024-09-28
 *
 * 功能:
 * - 从粘贴的文本中提取阿里云盘分享链接和提取码
 * - 批量打开链接并自动填充提取码
 * - 显示分享链接打开状态(成功/失败)
 *
 * 更新日志:
 * 1.0 (2024-09-28) - 初始版本
 *   - 实现基本的链接提取和批量打开功能
 *   - 添加自动填充提取码功能
 *   - 添加链接状态显示功能
 */
(function() {
    'use strict';

    let textarea, resultDiv;

    // 创建触发按钮
    function createTriggerButton() {
        const button = document.createElement('button');
        button.textContent = '批量打开';
        button.style.position = 'fixed';
        button.style.bottom = '20px';
        button.style.right = '20px';
        button.style.zIndex = '10000';
        button.style.padding = '10px 15px';
        button.style.backgroundColor = '#2196F3';
        button.style.color = 'white';
        button.style.border = 'none';
        button.style.borderRadius = '4px';
        button.style.cursor = 'pointer';
        button.style.fontSize = '14px';
        button.style.fontWeight = 'bold';
        button.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
        button.style.transition = 'background-color 0.3s';
        button.addEventListener('mouseover', () => button.style.backgroundColor = '#1976D2');
        button.addEventListener('mouseout', () => button.style.backgroundColor = '#2196F3');
        document.body.appendChild(button);
        return button;
    }

    // 创建UI元素
    function createUI() {
        const container = document.createElement('div');
        container.style.position = 'fixed';
        container.style.bottom = '80px';
        container.style.right = '20px';
        container.style.zIndex = '9999';
        container.style.backgroundColor = '#FFFFFF';
        container.style.padding = '20px';
        container.style.borderRadius = '8px';
        container.style.width = '650px';
        container.style.maxHeight = '80vh';
        container.style.overflowY = 'auto';
        container.style.display = 'none';
        container.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
        container.style.fontFamily = 'Arial, sans-serif';

        textarea = document.createElement('textarea');
        textarea.style.width = '100%';
        textarea.style.height = '200px';
        textarea.style.marginBottom = '15px';
        textarea.style.padding = '10px';
        textarea.style.border = '1px solid #E0E0E0';
        textarea.style.borderRadius = '4px';
        textarea.style.resize = 'vertical';
        textarea.placeholder = '粘贴包含阿里云盘链接的文本';

        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.justifyContent = 'space-between';
        buttonContainer.style.marginBottom = '15px';

        const leftButtonGroup = document.createElement('div');
        leftButtonGroup.style.display = 'flex';

        const rightButtonGroup = document.createElement('div');

        const createButton = (text, color) => {
            const button = document.createElement('button');
            button.textContent = text;
            button.style.padding = '8px 12px';
            button.style.backgroundColor = color;
            button.style.color = 'white';
            button.style.border = 'none';
            button.style.borderRadius = '4px';
            button.style.cursor = 'pointer';
            button.style.fontSize = '14px';
            button.style.fontWeight = 'bold';
            button.style.transition = 'background-color 0.3s';
            return button;
        };

        const extractButton = createButton('提取链接', '#4CAF50');
        const openAllButton = createButton('打开全部', '#2196F3');
        const resetButton = createButton('重置', '#F44336');

        extractButton.style.marginRight = '10px';

        [extractButton, openAllButton, resetButton].forEach(button => {
            button.addEventListener('mouseover', () => button.style.opacity = '0.8');
            button.addEventListener('mouseout', () => button.style.opacity = '1');
        });

        leftButtonGroup.appendChild(extractButton);
        leftButtonGroup.appendChild(openAllButton);
        rightButtonGroup.appendChild(resetButton);

        buttonContainer.appendChild(leftButtonGroup);
        buttonContainer.appendChild(rightButtonGroup);

        resultDiv = document.createElement('div');
        resultDiv.style.marginTop = '15px';

        container.appendChild(textarea);
        container.appendChild(buttonContainer);
        container.appendChild(resultDiv);

        document.body.appendChild(container);

        return { container, textarea, extractButton, resetButton, openAllButton, resultDiv };
    }

    // 提取链接和提取码
    function extractLinks(text) {
        const lines = text.split('\n');
        const linkLines = lines.filter(line =>
                                       line.includes('aliyundrive.com/s/') ||
                                       line.includes('alipan.com/s/')
                                      );

        const links = linkLines.map(line => {
            const match = line.match(/(https?:\/\/[^\s]+)/);
            return match ? match[1] : null;
        }).filter(link => link !== null);

        const results = links.map(link => {
            const linkIndex = text.indexOf(link);
            const afterLink = text.slice(linkIndex + link.length);
            const codeMatch = afterLink.match(/提取码:?\s*([a-zA-Z0-9]{4})/i);

            return {
                link: link,
                code: codeMatch ? codeMatch[1] : '无'
            };
        });

        return results;
    }

    // 打开链接
    function openLink(link, code, index) {
        GM_setValue('lastOpenedLink', link);
        GM_setValue('lastOpenedCode', code);
        GM_setValue('lastOpenedIndex', index);
        GM_setValue('linkStatus', '');  // 重置状态
        return new Promise((resolve) => {
            GM_openInTab(link, { active: true, insert: true, setParent: true });
            // 给足够的时间让新标签页加载并自动填充提取码
            setTimeout(resolve, 5000);
        });
    }

    // 保存数据到localStorage
    function saveData(text, links) {
        GM_setValue('extractorText', text);
        GM_setValue('extractorLinks', JSON.stringify(links));
    }

    // 从localStorage加载数据
    function loadData() {
        const text = GM_getValue('extractorText', '');
        const links = JSON.parse(GM_getValue('extractorLinks', '[]'));
        return { text, links };
    }

    // 新增:保存链接状态的函数
    function saveLinkStatus(index, status) {
        let linkStatuses = JSON.parse(GM_getValue('linkStatuses', '{}'));
        linkStatuses[index] = status;
        GM_setValue('linkStatuses', JSON.stringify(linkStatuses));
    }

    // 新增:获取链接状态的函数
    function getLinkStatus(index) {
        let linkStatuses = JSON.parse(GM_getValue('linkStatuses', '{}'));
        return linkStatuses[index];
    }

    // 更新UI
    function updateUI(textarea, resultDiv, text, links) {
        textarea.value = text;
        let resultHTML = '<ul style="list-style-type: none; padding: 0; margin: 0;">';
        links.forEach((item, index) => {
            const status = getLinkStatus(index);
            let backgroundColor = '#F5F5F5'; // 默认灰色
            if (status === 'success') {
                backgroundColor = '#C8E6C9'; // 浅绿色
            } else if (status === 'failed') {
                backgroundColor = '#FFCDD2'; // 浅红色
            }
            resultHTML += `
            <li id="link-item-${index}" style="margin-bottom: 10px; padding: 10px; background-color: ${backgroundColor}; border-radius: 4px;">
                <div style="display: flex; justify-content: space-between; align-items: center;">
                    <div style="flex-grow: 1; display: flex; justify-content: space-between; align-items: center;">
                        <span>
                            <span style="font-weight: bold;">链接 ${index + 1}:</span>
                            <span style="word-break: break-all; margin-left: 5px;">${item.link}</span>
                        </span>
                        <span style="margin-left: 10px;">提取码: ${item.code}</span>
                    </div>
                    <button class="open-copy-btn" data-index="${index}" style="background-color: #2196F3; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; white-space: nowrap; margin-left: 10px;">一键打开</button>
                </div>
            </li>`;
        });
        resultHTML += '</ul>';
        resultDiv.innerHTML = resultHTML;

        // 为每个"一键打开"按钮添加事件监听器
        document.querySelectorAll('.open-copy-btn').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const index = e.target.getAttribute('data-index');
                const item = links[index];
                openLink(item.link, item.code, index);
            });
            btn.addEventListener('mouseover', () => btn.style.opacity = '0.8');
            btn.addEventListener('mouseout', () => btn.style.opacity = '1');
        });
    }

    // 模拟用户输入
    function simulateUserInput(input, text) {
        return new Promise((resolve) => {
            input.focus();
            input.click();

            let i = 0;
            function typeChar() {
                if (i < text.length) {
                    const char = text[i];

                    input.dispatchEvent(new KeyboardEvent('keydown', { key: char, bubbles: true }));
                    input.dispatchEvent(new KeyboardEvent('keypress', { key: char, bubbles: true }));

                    const propDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
                    propDescriptor.set.call(input, input.value + char);

                    input.dispatchEvent(new Event('input', { bubbles: true }));
                    input.dispatchEvent(new KeyboardEvent('keyup', { key: char, bubbles: true }));

                    i++;
                    setTimeout(typeChar, 50 + Math.random() * 50);
                } else {
                    setTimeout(() => {
                        input.dispatchEvent(new Event('blur', { bubbles: true }));
                        input.dispatchEvent(new MouseEvent('click', { bubbles: true }));
                        input.focus();
                        input.dispatchEvent(new Event('input', { bubbles: true }));
                        resolve();
                    }, 100);
                }
            }
            setTimeout(typeChar, 500);
        });
    }

    // 自动填充提取码
    function autoFillCode() {
        const lastOpenedLink = GM_getValue('lastOpenedLink', '');
        const lastOpenedCode = GM_getValue('lastOpenedCode', '');

        if (window.location.href.startsWith(lastOpenedLink) && lastOpenedCode !== '') {
            const fillCode = async () => {
                // 检查是否已经在分享页面
                if (document.querySelector('button.button--WC7or.primary--NVxfK.medium--Pt0UL.btn-save--SqM8z')) {
                    GM_setValue('linkStatus', 'success');
                    return;
                }

                // 检查分享是否已失效
                if (document.querySelector('div.share-error-tips--2G2l6')) {
                    GM_setValue('linkStatus', 'failed');
                    return;
                }

                const codeInputs = document.querySelectorAll('input[placeholder="请输入提取码"], input.ant-input[placeholder="请输入提取码"]');
                if (codeInputs.length > 0) {
                    for (const input of codeInputs) {
                        await simulateUserInput(input, lastOpenedCode);
                        await waitForButtonEnabled();
                        const submitButton = document.querySelector('button.button--2ImPj[type="submit"]');
                        if (submitButton && submitButton.getAttribute('data-is-disabled') !== 'true') {
                            submitButton.click();
                            // 等待页面加载
                            setTimeout(() => {
                                if (document.querySelector('button.button--WC7or.primary--NVxfK.medium--Pt0UL.btn-save--SqM8z')) {
                                    GM_setValue('linkStatus', 'success');
                                } else {
                                    GM_setValue('linkStatus', 'failed');
                                }
                            }, 2000);
                        }
                    }
                } else {
                    setTimeout(fillCode, 1000);
                }
            };

            setTimeout(fillCode, 1000);
        }

        // 添加这一段来更新 UI
        setTimeout(() => {
            const links = JSON.parse(GM_getValue('extractorLinks', '[]'));
            updateUI(textarea, resultDiv, textarea.value, links);
        }, 3000);  // 给予足够的时间让状态更新
    }

    // 等待按钮变为可点击状态
    function waitForButtonEnabled() {
        return new Promise((resolve) => {
            const checkButton = () => {
                const submitButton = document.querySelector('button.button--2ImPj[type="submit"]');
                if (submitButton && submitButton.getAttribute('data-is-disabled') !== 'true') {
                    resolve();
                } else {
                    setTimeout(checkButton, 100);
                }
            };
            checkButton();
        });
    }

    // 更新链接状态
    function updateLinkStatus(index, status) {
        const linkItem = document.getElementById(`link-item-${index}`);
        if (linkItem) {
            if (status === 'success') {
                linkItem.style.backgroundColor = '#C8E6C9';  // 浅绿色
            } else if (status === 'failed') {
                linkItem.style.backgroundColor = '#FFCDD2';  // 浅红色
            }
            saveLinkStatus(index, status);
        } else {
        }
    }

    // 打开所有链接
    async function openAllLinks(links) {
        for (let i = 0; i < links.length; i++) {
            const item = links[i];
            await openLink(item.link, item.code, i);
        }
    }

    // 主函数
    function main() {
        const triggerButton = createTriggerButton();
        const { container, extractButton, resetButton, openAllButton } = createUI();

        // 加载保存的数据
        const { text, links } = loadData();
        updateUI(textarea, resultDiv, text, links);

        // 检查是否需要自动打开UI
        const lastOpenedLink = GM_getValue('lastOpenedLink', '');
        if (window.location.href === lastOpenedLink) {
            container.style.display = 'block';
        }

        triggerButton.addEventListener('click', () => {
            container.style.display = container.style.display === 'none' ? 'block' : 'none';
        });

        extractButton.addEventListener('click', () => {
            const text = textarea.value;
            try {
                const extractedLinks = extractLinks(text);
                updateUI(textarea, resultDiv, text, extractedLinks);
                saveData(text, extractedLinks);
            } catch (error) {
                console.error('Error during link extraction:', error);
                alert('提取链接时发生错误,请查看控制台以获取更多信息。');
            }
        });

        resetButton.addEventListener('click', () => {
            textarea.value = '';
            resultDiv.innerHTML = '';
            saveData('', []);
            GM_setValue('linkStatuses', '{}'); // 清除所有保存的链接状态
            GM_setValue('lastOpenedLink', ''); // 清除最后打开的链接
            GM_setValue('lastOpenedCode', ''); // 清除最后使用的提取码
            GM_setValue('lastOpenedIndex', ''); // 清除最后打开的链接索引
            GM_setValue('linkStatus', ''); // 清除链接状态
        });

        openAllButton.addEventListener('click', async () => {
            const links = JSON.parse(GM_getValue('extractorLinks', '[]'));
            await openAllLinks(links);
            // 所有链接打开后,更新 UI
            updateUI(textarea, resultDiv, textarea.value, links);
        });

        // 自动填充提取码
        autoFillCode();

        // 监听 URL 变化
        let lastUrl = location.href;
        new MutationObserver(() => {
            const url = location.href;
            if (url !== lastUrl) {
                lastUrl = url;
                autoFillCode();
            }
        }).observe(document, { subtree: true, childList: true });

        // 添加定时器来检查链接状态
        setInterval(() => {
            const linkStatus = GM_getValue('linkStatus', '');
            const lastOpenedIndex = GM_getValue('lastOpenedIndex', '');
            if (linkStatus && lastOpenedIndex !== '') {
                updateLinkStatus(lastOpenedIndex, linkStatus);
                // 重置状态
                GM_setValue('linkStatus', '');
                GM_setValue('lastOpenedIndex', '');
            }
        }, 1000);
    }

    // 运行脚本
    main();
})();