Greasy Fork

Greasy Fork is available in English.

定时点击下载苹果存档

使用苹果数据隐私创建你的数据备份时,如果你的照片很多,下载的文件就很多,挨个点击会费力。这个脚本就是帮助你自动定时点击。下载单个文件的时长就是需要设置的点击间隔时长。

// ==UserScript==
// @name         定时点击下载苹果存档
// @namespace    http://tampermonkey.net/
// @version      2025-05-15 update 2
// @description  使用苹果数据隐私创建你的数据备份时,如果你的照片很多,下载的文件就很多,挨个点击会费力。这个脚本就是帮助你自动定时点击。下载单个文件的时长就是需要设置的点击间隔时长。
// @author       You
// @match        https://privacy.apple.com/account
// @icon         https://www.google.com/s2/favicons?sz=64&domain=apple.com
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // 创建控制面板
    const panel = document.createElement('div');
    panel.style.cssText = `
        position: fixed;
        top: 10px;
        right: 10px;
        background: #fff;
        padding: 10px;
        border: 1px solid #ccc;
        border-radius: 5px;
        z-index: 9999;
    `;

    // 允许面板宽度可调整
    panel.style.resize = 'horizontal';
    panel.style.overflow = 'auto';
    panel.style.minWidth = '220px';
    panel.style.maxWidth = '600px';

    // 添加输入框用于设置间隔时间
    const intervalInput = document.createElement('input');
    intervalInput.type = 'number';
    intervalInput.value = '300';
    intervalInput.min = '1';
    intervalInput.style.width = '90px';
    intervalInput.style.marginRight = '10px';

    // 添加输入框用于设置起始位置
    const startPosInput = document.createElement('input');
    startPosInput.type = 'number';
    startPosInput.value = '0';
    startPosInput.min = '0';
    startPosInput.style.width = '90px';
    startPosInput.style.marginRight = '10px';

    // 添加输入框用于设置每批点击数量
    const batchCountInput = document.createElement('input');
    batchCountInput.type = 'number';
    batchCountInput.value = '3';
    batchCountInput.min = '1';
    batchCountInput.style.width = '90px';
    batchCountInput.style.marginRight = '10px';

    // 添加输入框用于设置每次点击间隔
    const clickDelayInput = document.createElement('input');
    clickDelayInput.type = 'number';
    clickDelayInput.value = '15000';
    clickDelayInput.min = '0';
    clickDelayInput.style.width = '90px';
    clickDelayInput.style.marginRight = '10px';

    // 添加当前位置显示
    const currentPosDisplay = document.createElement('div');
    currentPosDisplay.style.marginTop = '5px';
    currentPosDisplay.textContent = '当前位置: 0';

    // 添加开始按钮
    const startButton = document.createElement('button');
    startButton.textContent = '开始';
    startButton.style.marginRight = '10px';

    // 添加停止按钮
    const stopButton = document.createElement('button');
    stopButton.textContent = '停止';
    stopButton.disabled = true;

    // 将元素添加到面板中
    panel.appendChild(document.createTextNode('间隔秒数: '));
    panel.appendChild(intervalInput);
    panel.appendChild(document.createElement('br'));
    panel.appendChild(document.createTextNode('起始位置: '));
    panel.appendChild(startPosInput);
    panel.appendChild(document.createElement('br'));
    panel.appendChild(document.createTextNode('每批点击数量: '));
    panel.appendChild(batchCountInput);
    panel.appendChild(document.createElement('br'));
    panel.appendChild(document.createTextNode('每次点击间隔(ms): '));
    panel.appendChild(clickDelayInput);
    panel.appendChild(document.createElement('br'));
    panel.appendChild(currentPosDisplay);
    panel.appendChild(document.createElement('br'));
    panel.appendChild(startButton);
    panel.appendChild(stopButton);
    document.body.appendChild(panel);

    let intervalId = null;
    let currentPosition = 0;

    // 记录所有 setTimeout 任务ID
    let pendingTimeouts = [];

    // 读取 sessionStorage 配置
    const savedInterval = sessionStorage.getItem('apple_down_interval');
    const savedStartPos = sessionStorage.getItem('apple_down_startPos');
    const savedBatchCount = sessionStorage.getItem('apple_down_batchCount');
    const savedClickDelay = sessionStorage.getItem('apple_down_clickDelay');
    if (savedInterval !== null) intervalInput.value = savedInterval;
    if (savedStartPos !== null) startPosInput.value = savedStartPos;
    if (savedBatchCount !== null) batchCountInput.value = savedBatchCount;
    if (savedClickDelay !== null) clickDelayInput.value = savedClickDelay;

    // 输入框变动时保存配置
    intervalInput.addEventListener('input', () => {
        sessionStorage.setItem('apple_down_interval', intervalInput.value);
    });
    startPosInput.addEventListener('input', () => {
        sessionStorage.setItem('apple_down_startPos', startPosInput.value);
    });
    batchCountInput.addEventListener('input', () => {
        sessionStorage.setItem('apple_down_batchCount', batchCountInput.value);
    });
    clickDelayInput.addEventListener('input', () => {
        sessionStorage.setItem('apple_down_clickDelay', clickDelayInput.value);
    });

    // 点击下载按钮的函数
    function clickDownloadButtons() {
        const buttons = document.querySelectorAll('.archive-download-table button.button-link.download-button-link');
        if (currentPosition >= buttons.length) {
            stopAutoClick(); // 如果已经点完所有按钮,就停止
            return;
        }
        // 每次点击 batchCountInput.value 个按钮
        const batchCount = parseInt(batchCountInput.value, 10) || 1;
        const clickDelay = parseInt(clickDelayInput.value, 10) || 0;
        for (let i = 0; i < batchCount && currentPosition < buttons.length; i++) {
            ((btnIdx, delayIdx) => {
                const timeoutId = setTimeout(() => {
                    buttons[btnIdx].click();
                    // 执行后移除该timeoutId
                    pendingTimeouts = pendingTimeouts.filter(id => id !== timeoutId);
                    showPendingTimeouts(); // 立即刷新显示
                }, clickDelay * delayIdx);
                pendingTimeouts.push(timeoutId);
            })(currentPosition, i);
            currentPosition++;
        }
        currentPosDisplay.textContent = `当前位置: ${currentPosition}`;
        // 显示当前等待的 setTimeout 数量
        showPendingTimeouts();
    }

    // 显示当前等待的 setTimeout 任务ID列表
    function showPendingTimeouts() {
        let info = document.getElementById('pendingTimeoutsInfo');
        if (!info) {
            info = document.createElement('div');
            info.id = 'pendingTimeoutsInfo';
            info.style.fontSize = '12px';
            info.style.color = '#888';
            info.style.marginTop = '2px';
            currentPosDisplay.parentNode.insertBefore(info, currentPosDisplay.nextSibling);
        }
        if (pendingTimeouts.length === 0) {
            info.textContent = '无等待中的点击任务';
        } else {
            info.textContent = `等待中的点击任务ID: [${pendingTimeouts.join(', ')}]`;
        }
    }

    // 开始自动点击
    function startAutoClick() {
        const interval = parseInt(intervalInput.value, 10) * 1000; // 转换为毫秒
        if (interval < 1000) {
            alert('间隔时间不能小于1秒!');
            return;
        }

        currentPosition = parseInt(startPosInput.value, 10);
        clickDownloadButtons();

        intervalId = setInterval(clickDownloadButtons, interval);
        startButton.disabled = true;
        stopButton.disabled = false;
        intervalInput.disabled = true;
        startPosInput.disabled = true;
        batchCountInput.disabled = true;
        clickDelayInput.disabled = true;
    }

    // 停止自动点击
    function stopAutoClick() {
        if (intervalId) {
            clearInterval(intervalId);
            intervalId = null;
        }
        // 清理所有未执行的 setTimeout
        pendingTimeouts.forEach(id => clearTimeout(id));
        pendingTimeouts = [];
        showPendingTimeouts();
        startButton.disabled = false;
        stopButton.disabled = true;
        intervalInput.disabled = false;
        startPosInput.disabled = false;
        batchCountInput.disabled = false;
        clickDelayInput.disabled = false;
    }

    // 美化输入框和按钮样式
    const inputStyle = `
        padding: 4px 8px;
        border: 1px solid #bbb;
        border-radius: 4px;
        font-size: 14px;
        outline: none;
        margin-bottom: 4px;
        transition: border-color 0.2s;
    `;
    [intervalInput, startPosInput, batchCountInput, clickDelayInput].forEach(input => {
        input.style.cssText += inputStyle;
        input.addEventListener('focus', () => input.style.borderColor = '#007aff');
        input.addEventListener('blur', () => input.style.borderColor = '#bbb');
    });
    const buttonStyle = `
        padding: 6px 18px;
        border: none;
        border-radius: 4px;
        background: linear-gradient(90deg, #007aff 60%, #00c6fb 100%);
        color: #fff;
        font-size: 15px;
        cursor: pointer;
        box-shadow: 0 2px 8px rgba(0,0,0,0.07);
        margin-bottom: 4px;
        transition: background 0.2s, opacity 0.2s;
    `;
    [startButton, stopButton].forEach(btn => {
        btn.style.cssText += buttonStyle;
        btn.addEventListener('mouseenter', () => btn.style.opacity = '0.85');
        btn.addEventListener('mouseleave', () => btn.style.opacity = '1');
    });
    stopButton.style.background = 'linear-gradient(90deg, #ff3b30 60%, #ff7e5f 100%)';

    // 添加事件监听器
    startButton.addEventListener('click', startAutoClick);
    stopButton.addEventListener('click', stopAutoClick);

    [startButton, stopButton].forEach(btn => {
        const updateDisabledStyle = () => {
            // 只在禁用时加样式,启用时恢复原始样式
            if (btn.disabled) {
                btn.classList.add('apple-down-disabled-btn');
            } else {
                btn.classList.remove('apple-down-disabled-btn');
            }
        };
        updateDisabledStyle();
        const observer = new MutationObserver(updateDisabledStyle);
        observer.observe(btn, { attributes: true, attributeFilter: ['disabled'] });
    });
    // 用 class 控制禁用样式,避免样式累加导致按钮无法恢复
    const style = document.createElement('style');
    style.textContent = `
        .apple-down-disabled-btn {
            background: #ccc !important;
            color: #fff !important;
            cursor: not-allowed !important;
            opacity: 0.7 !important;
            box-shadow: none !important;
        }
    `;
    document.head.appendChild(style);
})();