Greasy Fork

Greasy Fork is available in English.

豆包&即梦AI下载无水印图片视频

实现豆包&即梦生成的图片视频免费无水印下载

目前为 2025-02-20 提交的版本,查看 最新版本

// ==UserScript==
// @name         豆包&即梦AI下载无水印图片视频
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  实现豆包&即梦生成的图片视频免费无水印下载
// @author       微信11208596
// @license      UNLICENSED
// @match        https://www.doubao.com/*
// @match        https://jimeng.jianying.com/ai-tool/*
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
    'use strict';

    // 添加激活码相关功能
    const ACTIVATION_KEY = 'doubao_activation_status';
    const SECRET_KEY = 'db94xy20240322'; // 使用一个固定的密钥值
    const VALID_DAYS = 30; // 激活码有效期(天)

    // 生成激活码
    function generateActivationCode(deviceId) {
        // 添加时间戳
        const timestamp = Math.floor(Date.now() / 1000);
        const expireTime = timestamp + (VALID_DAYS * 24 * 60 * 60);

        // 混合设备ID和时间戳
        const data = `${deviceId}|${expireTime}`;

        // 使用更复杂的加密算法
        const encrypted = encryptData(data);

        // 添加校验位
        const checksum = generateChecksum(encrypted);

        // 组合最终激活码
        return `${encrypted}-${checksum}-${expireTime.toString(36)}`;
    }

    // 验证激活码
    function verifyActivationCode(deviceId, inputCode) {
        try {
            // 检查激活码是否在黑名单中
            const blacklist = JSON.parse(localStorage.getItem('activation_blacklist') || '[]');
            if (blacklist.includes(inputCode)) {
                console.log('激活码已被取消');
                return false;
            }

            console.log('开始验证:', {deviceId, inputCode});

            const [encrypted, checksum, expireTimeStr] = inputCode.split('-');
            console.log('解析激活码:', {encrypted, checksum, expireTimeStr});

            const validChecksum = generateChecksum(encrypted);
            console.log('校验和:', {valid: validChecksum, input: checksum});

            if (generateChecksum(encrypted) !== checksum) {
                console.log('校验和不匹配');
                return false;
            }

            const expireTime = parseInt(expireTimeStr, 36);
            const now = Math.floor(Date.now() / 1000);
            console.log('时间验证:', {expireTime, now, valid: now <= expireTime});

            if (now > expireTime) {
                console.log('激活码已过期');
                return false;
            }

            const decrypted = decryptData(encrypted);
            console.log('解密结果:', decrypted);

            const [originalDeviceId, originalExpireTime] = decrypted.split('|');
            console.log('解析数据:', {originalDeviceId, originalExpireTime});

            const isValid = originalDeviceId === deviceId;
            console.log('设备ID匹配结果:', isValid);

            if (isValid) {
                // 保存激活码以便后续验证过期时间
                localStorage.setItem('activation_code', inputCode);
            }
            return isValid;
        } catch (e) {
            console.error('验证过程出错:', e);
            return false;
        }
    }

    // 加密函数
    function encryptData(data) {
        let result = '';
        const key = SECRET_KEY;

        // 使用简单的异或加密
        for (let i = 0; i < data.length; i++) {
            const charCode = data.charCodeAt(i);
            const keyChar = key.charCodeAt(i % key.length);
            const encrypted = (charCode ^ keyChar) % 256;
            result += encrypted.toString(16).padStart(2, '0');
        }

        return result;
    }

    // 解密函数
    function decryptData(encrypted) {
        let result = '';
        const key = SECRET_KEY;

        // 解密过程
        for (let i = 0; i < encrypted.length; i += 2) {
            const hex = encrypted.substr(i, 2);
            const encryptedChar = parseInt(hex, 16);
            const keyChar = key.charCodeAt((i/2) % key.length);
            const decrypted = (encryptedChar ^ keyChar) % 256;
            result += String.fromCharCode(decrypted);
        }

        return result;
    }

    // 生成校验位
    function generateChecksum(data) {
        let checksum = 0;
        for (let i = 0; i < data.length; i++) {
            checksum = ((checksum << 5) - checksum + data.charCodeAt(i)) >>> 0;
        }
        return checksum.toString(36);
    }

    // 检查激活状态
    function checkActivation() {
        const activationStatus = localStorage.getItem(ACTIVATION_KEY);
        const deviceId = localStorage.getItem('deviceId');
        const activationCode = localStorage.getItem('activation_code');

        if (!deviceId) {
            const newDeviceId = Math.random().toString(36).substring(2) + Date.now().toString(36);
            localStorage.setItem('deviceId', newDeviceId);
            return false;
        }

        // 检查激活码是否在黑名单中
        if (activationCode) {
            const blacklist = JSON.parse(localStorage.getItem('activation_blacklist') || '[]');
            if (blacklist.includes(activationCode)) {
                // 如果在黑名单中,立即清除激活状态
                localStorage.removeItem(ACTIVATION_KEY);
                localStorage.removeItem('activation_code');
                showFloatingTip('激活码已被禁用,请重新激活');
                return false;
            }
        }

        return activationStatus === 'activated' && deviceId && activationCode;
    }

    // 创建激活对话框
    function createActivationDialog() {
        const overlay = document.createElement('div');
        overlay.className = 'download-confirm-overlay';

        const dialog = document.createElement('div');
        dialog.className = 'download-confirm-dialog';

        const deviceId = localStorage.getItem('deviceId');

        // 检查是否已激活
        const isActivated = checkActivation();

        dialog.innerHTML = `
            <h3>软件激活</h3>
            ${isActivated ? `
                <p>软件已激活</p>
                <div class="buttons">
                    <button class="deactivate-btn" style="background: #ff3b30;">取消激活</button>
                </div>
            ` : `
                <p>请输入激活码以继续使用</p>
                <div class="input-container">
                    <label>设备ID</label>
                    <input type="text" id="deviceId" value="${deviceId}" readonly>
                    <div class="tip">请复制设备ID并联系微信(11208596)获取激活码</div>
                </div>
                <div class="input-container">
                    <label>激活码</label>
                    <input type="text" id="activationCode" placeholder="请输入激活码">
                </div>
                <div class="buttons">
                    <button class="cancel-btn">取消</button>
                    <button class="confirm-btn">激活</button>
                </div>
            `}
        `;

        document.body.appendChild(overlay);
        document.body.appendChild(dialog);

        const confirmBtn = dialog.querySelector('.confirm-btn');
        const cancelBtn = dialog.querySelector('.cancel-btn');
        const activationInput = dialog.querySelector('#activationCode');
        const deviceIdInput = dialog.querySelector('#deviceId');

        // 复制设备ID功能
        deviceIdInput.addEventListener('click', () => {
            deviceIdInput.select();
            document.execCommand('copy');
            showFloatingTip('设备ID已复制到剪贴板');
        });

        function closeDialog() {
            document.body.removeChild(overlay);
            document.body.removeChild(dialog);
        }

        if (isActivated) {
            const deactivateBtn = dialog.querySelector('.deactivate-btn');
            deactivateBtn.addEventListener('click', () => {
                if (confirm('确定要取消激活吗?取消后需要重新激活才能继续使用。')) {
                    localStorage.removeItem(ACTIVATION_KEY);
                    localStorage.removeItem('activation_code');
                    showFloatingTip('已取消激活');
                    closeDialog();
                }
            });
        } else {
            confirmBtn.addEventListener('click', () => {
                const code = activationInput.value.trim();
                if (!code) {
                    showFloatingTip('请输入激活码');
                    return;
                }

                confirmBtn.disabled = true;
                confirmBtn.textContent = '验证中...';

                // 验证激活码
                if (verifyActivationCode(deviceId, code)) {
                    localStorage.setItem(ACTIVATION_KEY, 'activated');
                    showFloatingTip('激活成功');
                    closeDialog();
                } else {
                    showFloatingTip('激活码无效');
                    confirmBtn.disabled = false;
                    confirmBtn.textContent = '激活';
                }
            });
        }

        cancelBtn.addEventListener('click', closeDialog);
    }

    // 添加一个激活码生成工具函数(仅供开发使用)
    function generateActivationCodeForDevice(deviceId) {
        return generateActivationCode(deviceId);
    }

    // 显示浮动提示
    function showFloatingTip(message) {
        const tip = document.createElement('div');
        tip.className = 'floating-tip';
        tip.innerHTML = `
            <div class="icon">i</div>
            <span>${message}</span>
        `;

        document.body.appendChild(tip);

        setTimeout(() => {
            tip.classList.add('show');
        }, 100);

        setTimeout(() => {
            tip.classList.remove('show');
            setTimeout(() => {
                document.body.removeChild(tip);
            }, 300);
        }, 3000);
    }

    // 将函数暴露到全局作用域
    window.deactivateCode = function(code) {
        if (!confirm('确定要取消此激活码吗?取消后此激活码将无法继续使用。')) {
            return;
        }

        try {
            // 将激活码添加到黑名单
            let blacklist = JSON.parse(localStorage.getItem('activation_blacklist') || '[]');
            if (!blacklist.includes(code)) {
                blacklist.push(code);
                localStorage.setItem('activation_blacklist', JSON.stringify(blacklist));

                // 如果当前用户正在使用这个激活码,立即取消激活
                const currentCode = localStorage.getItem('activation_code');
                if (currentCode === code) {
                    localStorage.removeItem(ACTIVATION_KEY);
                    localStorage.removeItem('activation_code');
                }

                // 广播黑名单更新事件
                window.dispatchEvent(new CustomEvent('blacklistUpdated', { detail: { code } }));
            }

            // 更新查询结果显示
            const resultDiv = document.getElementById('queryResult');
            resultDiv.innerHTML = `
                <div style="background: #f5f5f7; padding: 15px; border-radius: 8px;">
                    <div style="color: #ff3b30;">此激活码已被禁用,无法继续使用</div>
                </div>
            `;

            showFloatingTip('激活码已成功取消');

            // 强制刷新页面以确保状态更新
            setTimeout(() => {
                window.location.reload();
            }, 1500);
        } catch (e) {
            console.error('取消激活码失败:', e);
            showFloatingTip('取消激活码失败');
        }
    };

    // 添加黑名单更新监听器
    window.addEventListener('blacklistUpdated', function(event) {
        // 检查当前激活码是否被取消
        const currentCode = localStorage.getItem('activation_code');
        if (currentCode === event.detail.code) {
            localStorage.removeItem(ACTIVATION_KEY);
            localStorage.removeItem('activation_code');
            showFloatingTip('激活码已被禁用,请重新激活');
            window.location.reload();
        }
    });

    // 添加定期检查机制
    function startBlacklistCheck() {
        setInterval(() => {
            if (checkActivation() === false) {
                // 如果检查失败,可能需要刷新页面
                window.location.reload();
            }
        }, 30000); // 每30秒检查一次
    }

    // 在脚本初始化时启动检查
    startBlacklistCheck();

    window.queryActivationCode = function() {
        const code = document.getElementById('queryCode').value.trim();
        const resultDiv = document.getElementById('queryResult');

        if (!code) {
            showFloatingTip('请输入激活码');
            return;
        }

        try {
            // 首先检查黑名单
            const blacklist = JSON.parse(localStorage.getItem('activation_blacklist') || '[]');
            if (blacklist.includes(code)) {
                resultDiv.innerHTML = `
                    <div style="background: #f5f5f7; padding: 15px; border-radius: 8px;">
                        <div style="color: #ff3b30;">此激活码已被禁用,无法继续使用</div>
                    </div>
                `;
                resultDiv.style.display = 'block';
                return;
            }

            const [encrypted, checksum, expireTimeStr] = code.split('-');

            if (generateChecksum(encrypted) !== checksum) {
                resultDiv.innerHTML = '<div style="color: #ff3b30;">无效的激活码</div>';
                resultDiv.style.display = 'block';
                return;
            }

            const decrypted = decryptData(encrypted);
            const [deviceId, expireTime] = decrypted.split('|');

            const now = Math.floor(Date.now() / 1000);
            const expireTimeNum = parseInt(expireTimeStr, 36);
            const isExpired = now > expireTimeNum;

            resultDiv.innerHTML = `
                <div style="background: #f5f5f7; padding: 15px; border-radius: 8px;">
                    <div><strong>设备ID:</strong> ${deviceId}</div>
                    <div><strong>到期时间:</strong> ${new Date(expireTimeNum * 1000).toLocaleString()}</div>
                    <div><strong>状态:</strong> <span style="color: ${isExpired ? '#ff3b30' : '#00c853'}">${isExpired ? '已过期' : '有效'}</span></div>
                    ${!isExpired ? `
                        <div style="margin-top: 15px;">
                            <button onclick="deactivateCode('${code}')"
                                    style="background: #ff3b30; padding: 8px 16px; font-size: 13px;">
                                取消此激活码
                            </button>
                        </div>
                    ` : ''}
                </div>
            `;
            resultDiv.style.display = 'block';

        } catch (e) {
            resultDiv.innerHTML = '<div style="color: #ff3b30;">无效的激活码格式</div>';
            resultDiv.style.display = 'block';
        }
    };

    // 修改下载验证函数,添加强制刷新机制
    function downloadWithActivationCheck(mediaUrl, mediaType, downloadFunction) {
        // 首先检查是否有激活状态
        const isActivated = checkActivation();
        const activationCode = localStorage.getItem('activation_code');
        const deviceId = localStorage.getItem('deviceId');

        if (!isActivated || !activationCode || !deviceId) {
            createActivationDialog();
            return;
        }

        // 验证激活码
        try {
            // 1. 检查黑名单
            const blacklist = JSON.parse(localStorage.getItem('activation_blacklist') || '[]');
            if (blacklist.includes(activationCode)) {
                localStorage.removeItem(ACTIVATION_KEY);
                localStorage.removeItem('activation_code');
                showFloatingTip('激活码已被禁用,请重新激活');
                // 强制刷新页面
                setTimeout(() => {
                    window.location.reload();
                }, 1500);
                return;
            }

            // 2. 验证激活码格式和校验和
            const [encrypted, checksum, expireTimeStr] = activationCode.split('-');
            if (!encrypted || !checksum || !expireTimeStr || generateChecksum(encrypted) !== checksum) {
                localStorage.removeItem(ACTIVATION_KEY);
                localStorage.removeItem('activation_code');
                showFloatingTip('激活码无效,请重新激活');
                createActivationDialog();
                return;
            }

            // 3. 验证过期时间
            const expireTime = parseInt(expireTimeStr, 36);
            const now = Math.floor(Date.now() / 1000);
            if (now > expireTime) {
                localStorage.removeItem(ACTIVATION_KEY);
                localStorage.removeItem('activation_code');
                showFloatingTip('激活码已过期,请重新激活');
                createActivationDialog();
                return;
            }

            // 4. 验证设备ID匹配
            const decrypted = decryptData(encrypted);
            const [originalDeviceId] = decrypted.split('|');
            if (originalDeviceId !== deviceId) {
                localStorage.removeItem(ACTIVATION_KEY);
                localStorage.removeItem('activation_code');
                showFloatingTip('设备ID不匹配,请重新激活');
                createActivationDialog();
                return;
            }

            // 所有验证都通过,允许下载
            createConfirmDialog(mediaUrl, mediaType, downloadFunction);

        } catch (e) {
            console.error('验证激活码出错:', e);
            localStorage.removeItem(ACTIVATION_KEY);
            localStorage.removeItem('activation_code');
            showFloatingTip('激活码验证失败,请重新激活');
            createActivationDialog();
        }
    }

    // 修改确认对话框样式
    const style = document.createElement('style');
    style.textContent = `
        @keyframes dialogShow {
            from {
                opacity: 0;
                transform: translate(-50%, -48%) scale(0.96);
            }
            to {
                opacity: 1;
                transform: translate(-50%, -50%) scale(1);
            }
        }

        @keyframes overlayShow {
            from { opacity: 0; }
            to { opacity: 1; }
        }

        .download-confirm-dialog {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.8);
            backdrop-filter: blur(20px) saturate(180%);
            -webkit-backdrop-filter: blur(20px) saturate(180%);
            padding: 28px 24px;
            border-radius: 14px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12),
                        0 0 0 1px rgba(0, 0, 0, 0.05);
            z-index: 10000;
            min-width: 320px;
            max-width: 400px;
            text-align: center;
            font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
            animation: dialogShow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }

        .download-confirm-dialog h3 {
            margin: 0 0 8px 0;
            color: #1d1d1f;
            font-size: 19px;
            font-weight: 600;
            letter-spacing: -0.022em;
        }

        .download-confirm-dialog p {
            margin: 0 0 20px 0;
            color: #86868b;
            font-size: 14px;
            line-height: 1.4;
            letter-spacing: -0.016em;
        }

        .download-confirm-dialog .input-container {
            margin: 20px 0;
            text-align: left;
        }

        .download-confirm-dialog label {
            display: block;
            margin-bottom: 8px;
            color: #1d1d1f;
            font-size: 13px;
            font-weight: 500;
            letter-spacing: -0.016em;
        }

        .download-confirm-dialog input {
            width: 100%;
            padding: 12px 16px;
            border: 1px solid rgba(0, 0, 0, 0.1);
            border-radius: 10px;
            font-size: 15px;
            color: #1d1d1f;
            background: rgba(255, 255, 255, 0.8);
            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
            box-sizing: border-box;
        }

        .download-confirm-dialog input:focus {
            outline: none;
            border-color: #0071e3;
            box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.15);
            background: #ffffff;
        }

        .download-confirm-dialog .buttons {
            margin-top: 28px;
            display: flex;
            gap: 12px;
            justify-content: center;
        }

        .download-confirm-dialog button {
            min-width: 128px;
            padding: 12px 24px;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            letter-spacing: -0.016em;
            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
        }

        .download-confirm-dialog .confirm-btn {
            background: #0071e3;
            color: white;
            transform: scale(1);
        }

        .download-confirm-dialog .confirm-btn:hover {
            background: #0077ED;
            transform: scale(1.02);
        }

        .download-confirm-dialog .confirm-btn:active {
            transform: scale(0.98);
        }

        .download-confirm-dialog .confirm-btn:disabled {
            background: #999999;
            cursor: not-allowed;
            opacity: 0.7;
            transform: scale(1);
        }

        .download-confirm-dialog .cancel-btn {
            background: rgba(0, 0, 0, 0.05);
            color: #1d1d1f;
            backdrop-filter: blur(20px);
            -webkit-backdrop-filter: blur(20px);
        }

        .download-confirm-dialog .cancel-btn:hover {
            background: rgba(0, 0, 0, 0.1);
        }

        .download-confirm-dialog .cancel-btn:active {
            background: rgba(0, 0, 0, 0.15);
        }

        .download-confirm-overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.4);
            backdrop-filter: blur(5px);
            -webkit-backdrop-filter: blur(5px);
            z-index: 9999;
            animation: overlayShow 0.3s ease-out;
        }

        @media (prefers-color-scheme: dark) {
            .download-confirm-dialog {
                background: rgba(40, 40, 45, 0.8);
            }

            .download-confirm-dialog h3 {
                color: #ffffff;
            }

            .download-confirm-dialog p {
                color: #98989d;
            }

            .download-confirm-dialog label {
                color: #ffffff;
            }

            .download-confirm-dialog input {
                background: rgba(60, 60, 65, 0.8);
                border-color: rgba(255, 255, 255, 0.1);
                color: #ffffff;
            }

            .download-confirm-dialog input:focus {
                background: rgba(70, 70, 75, 0.8);
            }

            .download-confirm-dialog .cancel-btn {
                background: rgba(255, 255, 255, 0.1);
                color: #ffffff;
            }

            .download-confirm-dialog .cancel-btn:hover {
                background: rgba(255, 255, 255, 0.15);
            }
        }

        .download-confirm-dialog .tip {
            font-size: 12px;
            color: #86868b;
            margin-top: 6px;
            text-align: left;
        }

        .download-confirm-dialog .progress-text {
            margin-top: 12px;
            font-size: 13px;
            color: #1d1d1f;
            letter-spacing: -0.016em;
        }

        .download-confirm-dialog .success-icon {
            display: inline-block;
            width: 16px;
            height: 16px;
            border-radius: 50%;
            background: #00c853;
            position: relative;
            margin-right: 6px;
            transform: scale(0);
            transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
        }

        .download-confirm-dialog .success-icon:after {
            content: '';
            position: absolute;
            width: 8px;
            height: 4px;
            border: 2px solid white;
            border-top: 0;
            border-right: 0;
            transform: rotate(-45deg);
            top: 4px;
            left: 4px;
        }

        .download-confirm-dialog .success-icon.show {
            transform: scale(1);
        }

        @media (prefers-color-scheme: dark) {
            .download-confirm-dialog .tip {
                color: #98989d;
            }
            .download-confirm-dialog .progress-text {
                color: #ffffff;
            }
        }

        .floating-tip {
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%) translateY(100px);
            background: rgba(0, 0, 0, 0.8);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            padding: 12px 20px;
            border-radius: 10px;
            color: white;
            font-size: 14px;
            z-index: 9999;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            opacity: 0;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
            display: flex;
            align-items: center;
            gap: 8px;
            pointer-events: none;
        }

        .floating-tip.show {
            transform: translateX(-50%) translateY(0);
            opacity: 1;
        }

        .floating-tip .icon {
            width: 18px;
            height: 18px;
            background: #fff;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
            font-size: 12px;
            color: #000;
        }

        @media (prefers-color-scheme: dark) {
            .floating-tip {
                background: rgba(255, 255, 255, 0.9);
                color: #1d1d1f;
            }
            .floating-tip .icon {
                background: #1d1d1f;
                color: #fff;
            }
        }

        .usage-tip {
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%) translateY(-100px);
            background: rgba(0, 0, 0, 0.9);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            padding: 16px 24px;
            border-radius: 12px;
            color: white;
            font-size: 15px;
            line-height: 1.4;
            z-index: 9999;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
            opacity: 0;
            transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
            font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
            display: flex;
            align-items: center;
            gap: 12px;
            cursor: pointer;
            max-width: 90%;
            width: auto;
            border: 1px solid rgba(255, 255, 255, 0.1);
        }

        .usage-tip.show {
            transform: translateX(-50%) translateY(0);
            opacity: 1;
        }

        .usage-tip .icon {
            font-size: 24px;
            flex-shrink: 0;
        }

        .usage-tip .content {
            display: flex;
            flex-direction: column;
            gap: 4px;
        }

        .usage-tip .main-text {
            font-weight: 500;
        }

        .usage-tip .contact {
            font-size: 13px;
            color: rgba(255, 255, 255, 0.8);
        }

        @media (prefers-color-scheme: dark) {
            .usage-tip {
                background: rgba(255, 255, 255, 0.95);
                color: #1d1d1f;
                border: 1px solid rgba(0, 0, 0, 0.1);
            }
            .usage-tip .contact {
                color: rgba(0, 0, 0, 0.6);
            }
        }

        .remaining-time {
            background: rgba(0, 0, 0, 0.03);
            padding: 8px 12px;
            border-radius: 8px;
            text-align: center;
        }

        @media (prefers-color-scheme: dark) {
            .remaining-time {
                background: rgba(255, 255, 255, 0.05);
                color: #98989d;
            }
        }
    `;
    document.head.appendChild(style);

    // 获取当前网站域名
    const currentDomain = window.location.hostname;

    // 创建确认对话框
    function createConfirmDialog(mediaUrl, mediaType, downloadFunction) {
        const overlay = document.createElement('div');
        overlay.className = 'download-confirm-overlay';

        const dialog = document.createElement('div');
        dialog.className = 'download-confirm-dialog';

        // 获取激活码和过期时间信息
        const activationCode = localStorage.getItem('activation_code');
        let remainingTimeText = '';

        if (activationCode) {
            try {
                const [, , expireTimeStr] = activationCode.split('-');
                const expireTime = parseInt(expireTimeStr, 36);
                const now = Math.floor(Date.now() / 1000);
                const remainingSeconds = expireTime - now;

                if (remainingSeconds > 0) {
                    const days = Math.floor(remainingSeconds / (24 * 60 * 60));
                    const hours = Math.floor((remainingSeconds % (24 * 60 * 60)) / (60 * 60));
                    const minutes = Math.floor((remainingSeconds % (60 * 60)) / 60);

                    remainingTimeText = `
                        <div class="remaining-time" style="margin-bottom: 15px; color: #86868b; font-size: 13px;">
                            激活码剩余时间:${days}天${hours}小时${minutes}分钟
                        </div>
                    `;
                }
            } catch (e) {
                console.error('解析过期时间出错:', e);
            }
        }

        // 获取当前日期时间作为默认文件名的备选
        const now = new Date();
        const dateStr = `${now.getFullYear()}${(now.getMonth()+1).toString().padStart(2,'0')}${now.getDate().toString().padStart(2,'0')}`;

        // 获取提示词
        let promptText = '';
        // 根据不同类型使用不同的选择器
        let promptElement;
        if (mediaType === 'video') {
            // 视频页面的提示词选择器
            promptElement = document.querySelector('span.lv-typography[class*="promptText-sTGKI"]');
        } else {
            // 图片页面的提示词选择器 - 添加豆包新的选择器
            promptElement = document.querySelector('span.lv-typography[class*="promptText-"]') ||
                           document.querySelector('.message-text-aF_36u[data-testid="message_text_content"]');
        }

        if (promptElement) {
            // 获取完整的提示词文本
            let text = promptElement.textContent.trim();

            // 如果是豆包的提示词,移除"帮我生成图片:"前缀
            if (text.startsWith('帮我生成图片:')) {
                text = text.replace('帮我生成图片:', '');
            }

            promptText = text
                // 移除多余的空格和换行
                .replace(/\s+/g, ' ')
                // 移除引号和破折号
                .replace(/^[""]-|[""]$/g, '')
                // 移除文件名中的非法字符
                .replace(/[\\/:*?"<>|]/g, '_')
                // 移除比例信息
                .replace(/比例["\d+:\d+"]/, '')
                .trim()
                .substring(0, 100); // 限制长度

            console.log(`获取到的${mediaType}提示词:`, promptText); // 调试用
        } else {
            console.log(`未找到${mediaType}提示词元素`); // 调试用
        }

        // 默认文件名使用提示词,如果没有提示词则使用日期
        const defaultFileName = promptText || dateStr;

        // 修改对话框内容,添加剩余时间显示
        dialog.innerHTML = `
            <h3>下载${mediaType === 'video' ? '视频' : '图片'}</h3>
            ${remainingTimeText}
            <p>请确认下载信息</p>
            <div class="input-container">
                <label for="fileName">文件名称</label>
                <input type="text"
                       id="fileName"
                       value="${defaultFileName}"
                       placeholder="请输入文件名称"
                       spellcheck="false"
                       autocomplete="off">
                <div class="tip">提示:右键点击${mediaType === 'video' ? '视频' : '图片'}即可下载,文件名将自动使用AI提示词</div>
            </div>
            <div class="progress-text" style="display: none;">
                <span class="success-icon"></span>
                <span class="status-text"></span>
            </div>
            <div class="buttons">
                <button class="cancel-btn">取消</button>
                <button class="confirm-btn">下载</button>
            </div>
        `;

        document.body.appendChild(overlay);
        document.body.appendChild(dialog);

        const confirmBtn = dialog.querySelector('.confirm-btn');
        const cancelBtn = dialog.querySelector('.cancel-btn');
        const fileNameInput = dialog.querySelector('#fileName');

        const progressText = dialog.querySelector('.progress-text');
        const statusText = dialog.querySelector('.status-text');
        const successIcon = dialog.querySelector('.success-icon');

        function closeDialog() {
            document.body.removeChild(overlay);
            document.body.removeChild(dialog);
        }

        function handleDownloadProgress(percent) {
            if (percent) {
                progressText.style.display = 'block';
                statusText.textContent = `正在下载...${percent}%`;
            }
        }

        function handleDownloadSuccess() {
            confirmBtn.style.display = 'none';
            progressText.style.display = 'block';
            successIcon.classList.add('show');
            statusText.textContent = '下载完成';
            setTimeout(() => {
                closeDialog();
            }, 1500);
        }

        function handleDownloadError(error) {
            progressText.style.display = 'block';
            statusText.textContent = `下载失败: ${error}`;
            statusText.style.color = '#ff3b30';
            confirmBtn.disabled = false;
            confirmBtn.textContent = '重试';
        }

        confirmBtn.addEventListener('click', () => {
            confirmBtn.disabled = true;
            confirmBtn.textContent = '准备下载...';
            const customFileName = fileNameInput.value.trim();

            downloadFunction(
                mediaUrl,
                handleDownloadSuccess,
                customFileName,
                handleDownloadProgress,
                handleDownloadError
            );
        });

        cancelBtn.addEventListener('click', closeDialog);
    }

    // 处理视频URL,移除水印
    function processVideoUrl(url) {
        try {
            if (url.includes('vlabvod.com')) {
                const urlObj = new URL(url);
                const paramsToRemove = [
                    'lr', 'watermark', 'display_watermark_busi_user',
                    'cd', 'cs', 'ds', 'ft', 'btag', 'dy_q', 'feature_id'
                ];

                paramsToRemove.forEach(param => {
                    urlObj.searchParams.delete(param);
                });

                if (urlObj.searchParams.has('br')) {
                    const br = parseInt(urlObj.searchParams.get('br'));
                    urlObj.searchParams.set('br', Math.max(br, 6000).toString());
                    urlObj.searchParams.set('bt', Math.max(br, 6000).toString());
                }

                urlObj.searchParams.delete('l');
                return urlObj.toString();
            }
            return url;
        } catch (e) {
            console.error('处理视频URL时出错:', e);
            return url;
        }
    }

    // 获取真实视频URL
    async function getRealVideoUrl(videoElement) {
        let videoUrl = videoElement.src;
        if (!videoUrl) {
            const sourceElement = videoElement.querySelector('source');
            if (sourceElement) {
                videoUrl = sourceElement.src;
            }
        }
        if (!videoUrl) {
            videoUrl = videoElement.getAttribute('data-src');
        }
        return videoUrl;
    }

    // 获取文件扩展名
    function getFileExtension(url) {
        const extension = url.split('?')[0].match(/\.(jpg|jpeg|png|gif|mp4|webm)$/i);
        return extension ? extension[0] : '.jpg';
    }

    // 下载图片的函数
    function downloadImage(imageUrl, callback, customFileName, onProgress, onError) {
        const fileExtension = getFileExtension(imageUrl);
        const fileName = customFileName ? `${customFileName}${fileExtension}` : getFileNameFromUrl(imageUrl);

        GM_xmlhttpRequest({
            method: 'GET',
            url: imageUrl,
            responseType: 'blob',
            headers: {
                'Accept': 'image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
                'Accept-Encoding': 'gzip, deflate, br',
                'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
                'Referer': currentDomain.includes('doubao') ?
                          'https://www.doubao.com/' :
                          'https://jimeng.jianying.com/',
                'Origin': currentDomain.includes('doubao') ?
                         'https://www.doubao.com' :
                         'https://jimeng.jianying.com',
                'User-Agent': navigator.userAgent
            },
            onprogress: function(progress) {
                if (progress.lengthComputable) {
                    const percent = Math.round((progress.loaded / progress.total) * 100);
                    if (onProgress) onProgress(percent);
                }
            },
            onload: function(response) {
                if (response.status === 200) {
                    const blob = response.response;
                    const url = URL.createObjectURL(blob);
                    const link = document.createElement('a');
                    link.href = url;
                    link.download = fileName;
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);
                    setTimeout(() => URL.revokeObjectURL(url), 100);
                    if (callback) callback();
                } else {
                    if (onError) onError(`HTTP ${response.status}`);
                }
            },
            onerror: function(error) {
                if (onError) onError(error.message || '网络错误');
            }
        });
    }

    // 下载视频的函数
    function downloadVideo(videoUrl, callback, customFileName, onProgress, onError) {
        const processedUrl = processVideoUrl(videoUrl);
        const fileExtension = '.mp4';
        const fileName = customFileName ? `${customFileName}${fileExtension}` : getFileNameFromUrl(processedUrl);

        let progressDialog = document.querySelector('.download-confirm-dialog');
        let progressBtn = progressDialog?.querySelector('.confirm-btn');

        GM_xmlhttpRequest({
            method: 'GET',
            url: processedUrl,
            responseType: 'blob',
            headers: {
                'Accept': 'video/mp4,video/*;q=0.9,*/*;q=0.8',
                'Accept-Encoding': 'gzip, deflate, br',
                'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
                'Range': 'bytes=0-',
                'Referer': currentDomain.includes('doubao') ?
                          'https://www.doubao.com/' :
                          'https://jimeng.jianying.com/',
                'Origin': currentDomain.includes('doubao') ?
                         'https://www.doubao.com' :
                         'https://jimeng.jianying.com',
                'User-Agent': navigator.userAgent
            },
            onprogress: function(progress) {
                if (progress.lengthComputable && progressBtn) {
                    const percent = Math.round((progress.loaded / progress.total) * 100);
                    progressBtn.textContent = `下载中 ${percent}%`;
                }
            },
            onload: function(response) {
                if (response.status === 200 || response.status === 206) {
                    const blob = response.response;
                    const url = URL.createObjectURL(blob);
                    const link = document.createElement('a');
                    link.href = url;
                    link.download = fileName;
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);
                    setTimeout(() => URL.revokeObjectURL(url), 100);
                    if (callback) callback();
                } else {
                    console.error('下载失败:', response.status);
                    alert('下载失败,请检查控制台获取详细信息');
                    if (callback) callback();
                }
            },
            onerror: function(error) {
                console.error('请求失败:', error);
                alert('下载失败,请检查控制台获取详细信息');
                if (callback) callback();
            }
        });
    }

    // 从 URL 中提取文件名
    function getFileNameFromUrl(url) {
        url = url.split('?')[0];
        const urlParts = url.split('/');
        let fileName = urlParts[urlParts.length - 1];

        if (fileName.includes('~')) {
            fileName = fileName.split('~')[0];
        }

        if (!fileName.match(/\.(mp4|webm|jpg|jpeg|png)$/i)) {
            fileName += url.includes('video') ? '.mp4' : '.jpeg';
        }

        return fileName;
    }

    // 修改事件监听器,使用新的下载函数
    document.addEventListener('contextmenu', async function (event) {
        const target = event.target;

        if (target.tagName.toLowerCase() === 'img') {
            const imageUrl = target.src;
            if (imageUrl) {
                downloadWithActivationCheck(imageUrl, 'image', downloadImage);
                event.preventDefault();
            }
        }
        else if (target.tagName.toLowerCase() === 'video' ||
                 target.closest('video')) {
            const videoElement = target.tagName.toLowerCase() === 'video' ?
                               target : target.closest('video');

            if (videoElement) {
                const videoUrl = await getRealVideoUrl(videoElement);
                if (videoUrl) {
                    downloadWithActivationCheck(videoUrl, 'video', downloadVideo);
                    event.preventDefault();
                }
            }
        }
    }, true);

    // 修改显示提示的函数
    function showUsageTip() {
        // 移除检查localStorage的逻辑,每次都显示提示
        const tip = document.createElement('div');
        tip.className = 'usage-tip';
        tip.innerHTML = `
            <span class="icon">💡</span>
            <div class="content">
                <span class="main-text">点击图片或视频,单击鼠标右键即可免费下载无水印的图片或视频</span>
                <span class="contact">有问题联系微信:11208596</span>
            </div>
        `;
        document.body.appendChild(tip);

        // 显示提示
        setTimeout(() => {
            tip.classList.add('show');
        }, 500);

        // 10秒后自动隐藏提示
        setTimeout(() => {
            tip.classList.remove('show');
            setTimeout(() => {
                document.body.removeChild(tip);
            }, 600);
        }, 10000);

        // 点击可以提前关闭提示
        tip.addEventListener('click', () => {
            tip.classList.remove('show');
            setTimeout(() => {
                document.body.removeChild(tip);
            }, 600);
        });
    }

    // 修改页面加载时的提示逻辑
    function initUsageTip() {
        if (window.location.hostname.includes('doubao.com') ||
            window.location.hostname.includes('jimeng.jianying.com')) {

            // 页面加载完成后显示提示
            if (document.readyState === 'complete') {
                showUsageTip();
            } else {
                window.addEventListener('load', showUsageTip);
            }

            // 监听页面可见性变化
            document.addEventListener('visibilitychange', () => {
                if (document.visibilityState === 'visible') {
                    showUsageTip();
                }
            });

            // 监听页面焦点变化
            window.addEventListener('focus', showUsageTip);
        }
    }

    // 初始化提示
    initUsageTip();
})();