Y2K Image Uploader

Paste or Drag images to upload to your Y2K VPS and get Markdown links immediately.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Y2K Image Uploader
// @namespace    http://tampermonkey.net/
// @version      3.1
// @description  Paste or Drag images to upload to your Y2K VPS and get Markdown links immediately.
// @author       You
// @match        *://www.nodeseek.com/*
// @match        *://nodeseek.com/*
// @match        *://y2k.cxary.dpdns.org/*
// @match        *://*.y2k.cxary.dpdns.org/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setClipboard
// @grant        GM_notification
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @connect      y2k.cxary.dpdns.org
// @license      MIT
// ==/UserScript==
(function () {
    'use strict';

    // ===== 全局配置 (Global Configuration) =====
    const APP = {
        api: {
            key: GM_getValue('y2k_apiKey', ''),
            setKey: key => {
                GM_setValue('y2k_apiKey', key);
                APP.api.key = key;
                UI.updateState();
            },
            clearKey: () => {
                GM_deleteValue('y2k_apiKey');
                APP.api.key = '';
                UI.updateState();
            },
            endpoints: {
                upload: 'https://y2k.cxary.dpdns.org/api/images',
                batchUpload: 'https://y2k.cxary.dpdns.org/api/images/batch',
                apiKey: 'https://y2k.cxary.dpdns.org/api/auth/user/api-key',
                me: 'https://y2k.cxary.dpdns.org/api/auth/me'
            }
        },
        site: {
            url: 'https://y2k.cxary.dpdns.org'
        },
        storage: {
            keys: {
                loginCheck: 'y2k_login_check',
                loginStatus: 'y2k_login_status',
                logout: 'y2k_logout',
                guestToken: 'guest_token'
            },
            get: key => localStorage.getItem(APP.storage.keys[key]),
            set: (key, value) => localStorage.setItem(APP.storage.keys[key], value),
            remove: key => localStorage.removeItem(APP.storage.keys[key])
        },
        user: {
            id: GM_getValue('y2k_userId', ''),
            identifier: GM_getValue('y2k_userIdentifier', ''),
            role: GM_getValue('y2k_userRole', ''),
            setInfo: (id, identifier, role) => {
                GM_setValue('y2k_userId', id);
                GM_setValue('y2k_userIdentifier', identifier);
                GM_setValue('y2k_userRole', role);
                APP.user.id = id;
                APP.user.identifier = identifier;
                APP.user.role = role;
            },
            clearInfo: () => {
                GM_deleteValue('y2k_userId');
                GM_deleteValue('y2k_userIdentifier');
                GM_deleteValue('y2k_userRole');
                APP.user.id = '';
                APP.user.identifier = '';
                APP.user.role = '';
            }
        },
        // 跨域游客 Token 存储 (使用 GM 存储跨域共享)
        guest: {
            getToken: () => {
                // 优先从 GM 存储读取(跨域)
                const gmToken = GM_getValue('y2k_guest_token', '');
                if (gmToken) return gmToken;
                // 回退到 localStorage(同域)
                return localStorage.getItem('guest_token') || '';
            },
            setToken: (token) => {
                GM_setValue('y2k_guest_token', token);
            },
            clearToken: () => {
                GM_deleteValue('y2k_guest_token');
            }
        },
        retry: {
            max: 2,
            delay: 1000
        },
        statusTimeout: 2000,
        auth: {
            recentLoginGracePeriod: 30000,
            loginCheckInterval: 3000,
            loginCheckTimeout: 300000,
            accountCheckInterval: 10000 // 账户变化检测间隔
        }
    };

    // State
    let uploadMode = true;
    let dragCounter = 0;
    let autoCloseTimer;
    let pendingFiles = [];
    let uploadResults = [];

    // ===== 工具函数 (Utility Functions) =====
    const Utils = {
        isY2KSite: () => /y2k\.cxary\.dpdns\.org$/.test(window.location.hostname),
        delay: ms => new Promise(r => setTimeout(r, ms))
    };

    // ===== API通信 (API Communication) =====
    const API = {
        request: ({ url, method = 'GET', data = null, headers = {}, withAuth = false }) => {
            console.log(`[Y2K] [API] Request: ${method} ${url}`);
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method,
                    url,
                    headers: {
                        'Accept': 'application/json',
                        ...(withAuth && APP.api.key ? { 'X-API-Key': APP.api.key } : {}),
                        ...headers
                    },
                    data,
                    withCredentials: true,
                    // 不使用 json 类型,手动解析以防万一
                    onload: response => {
                        console.log(`[Y2K] [API] Response ${response.status} for ${url}`);
                        try {
                            const resData = JSON.parse(response.responseText);
                            if (response.status >= 200 && response.status < 300) {
                                resolve(resData);
                            } else {
                                console.error(`[Y2K] [API] Error status ${response.status}:`, resData);
                                reject(resData);
                            }
                        } catch (e) {
                            console.error(`[Y2K] [API] Parse error for ${url}:`, response.responseText);
                            reject({ error: 'Invalid JSON response' });
                        }
                    },
                    onerror: err => {
                        console.error(`[Y2K] [API] Network Error for ${url}:`, err);
                        reject(err);
                    }
                });
            });
        },

        checkLoginAndGetKey: async () => {
            try {
                // 1. 首先检查游客 Token
                const guestToken = APP.guest.getToken();
                if (guestToken && guestToken.startsWith('guest_')) {
                    console.log('[Y2K] Detected guest token, verifying...');
                    try {
                        // 验证游客 Token 是否有效
                        const response = await API.request({
                            url: APP.api.endpoints.me,
                            headers: { 'Authorization': `Bearer ${guestToken}` }
                        });
                        if (response && response.success && response.user) {
                            console.log('[Y2K] Guest token verified:', response.user.identifier);
                            // 游客不需要 API Key,使用 Token 直接认证
                            // 设置一个特殊标记表示游客已登录
                            APP.api.key = `guest:${guestToken}`;
                            return true;
                        }
                    } catch (e) {
                        console.warn('[Y2K] Guest token invalid:', e);
                    }
                }

                // 2. 检查 Supabase Token (普通用户)
                const projectRef = 'imldlbilfdrorglhpwnh';
                const storageKey = `sb-${projectRef}-auth-token`;
                const fallbackKey = 'sb-access-token';
                const tokenData = localStorage.getItem(storageKey) || localStorage.getItem(fallbackKey);

                if (!tokenData) {
                    return false;
                }

                let token = '';
                try {
                    // Supabase 存储可能是 JSON 字符串
                    const parsed = JSON.parse(tokenData);
                    token = parsed.access_token || parsed;
                } catch (e) {
                    token = tokenData; // 可能是原始字符串
                }

                if (!token) {
                    console.error('[Y2K] Sync Attempt: Extracted token is empty.');
                    return false;
                }

                console.log('[Y2K] Sync Attempt: Requesting API Key...');
                const response = await API.request({
                    url: APP.api.endpoints.apiKey,
                    headers: { 'Authorization': `Bearer ${token}` }
                });

                if (response && response.api_key) {
                    console.log('[Y2K] Sync Attempt: Success! API Key obtained.');
                    APP.api.setKey(response.api_key);
                    return true;
                }

                console.warn('[Y2K] Sync Attempt: API returned success but no key:', response);
                return false;
            } catch (error) {
                console.error('[Y2K] Sync Attempt: Critical error:', error);
                APP.api.clearKey();
                return false;
            }
        },

        // 获取当前用户信息
        getUserInfo: async () => {
            try {
                // 检查是否有凭据
                if (APP.api.key) {
                    let headers = {};
                    if (APP.api.key.startsWith('guest:')) {
                        // 游客模式:使用 Authorization Bearer
                        const guestToken = APP.api.key.replace('guest:', '');
                        headers['Authorization'] = `Bearer ${guestToken}`;
                    } else {
                        // 普通用户模式:使用 X-API-Key
                        headers['X-API-Key'] = APP.api.key;
                    }

                    const response = await API.request({
                        url: APP.api.endpoints.me,
                        headers
                    });
                    if (response && response.success && response.user) {
                        return response.user;
                    }
                }

                // 回退:尝试从 localStorage 读取游客 Token
                const guestToken = APP.guest.getToken();
                if (guestToken) {
                    const response = await API.request({
                        url: APP.api.endpoints.me,
                        headers: { 'Authorization': `Bearer ${guestToken}` }
                    });
                    if (response && response.success && response.user) {
                        return response.user;
                    }
                }

                return null;
            } catch (error) {
                console.error('[Y2K] getUserInfo error:', error);
                return null;
            }
        },

        // 从 localStorage 获取当前 Token 中的用户ID
        getCurrentTokenUserId: () => {
            // 检查 Supabase Token
            const projectRef = 'imldlbilfdrorglhpwnh';
            const storageKey = `sb-${projectRef}-auth-token`;
            const tokenData = localStorage.getItem(storageKey);

            if (tokenData) {
                try {
                    const parsed = JSON.parse(tokenData);
                    if (parsed.user && parsed.user.id) {
                        return { id: parsed.user.id, role: 'user' };
                    }
                } catch (e) { }
            }

            // 检查游客 Token
            const guestToken = APP.guest.getToken();
            if (guestToken && guestToken.startsWith('guest_')) {
                // 从 guest token 提取 ID (格式: guest_uuid)
                return { id: guestToken.replace('guest_', ''), role: 'guest' };
            }

            return null;
        }
    };

    // ===== UI与状态管理 (UI & Status Management) =====
    const STATUS = {
        SUCCESS: { class: 'success', color: '#4ade80' },
        ERROR: { class: 'error', color: '#ef4444' },
        WARNING: { class: 'warning', color: '#e6a23c' },
        INFO: { class: 'info', color: '#1890ff' },
        MISMATCH: { class: 'mismatch', color: '#ffa500' }
    };

    const MESSAGE = {
        READY: 'Y2K已就绪',
        UPLOADING: '正在上传...',
        UPLOAD_SUCCESS: '上传成功!',
        LOGIN_EXPIRED: '登录已失效',
        LOGOUT: '已退出登录',
        RETRY: (current, max) => `重试上传 (${current}/${max})`,
        ACCOUNT_MISMATCH: '账户已切换',
        REFRESHING: '正在刷新...'
    };

    // ===== 认证管理 (Authentication Management) =====
    const Auth = {
        checkLoginIfNeeded: async (forceCheck = false) => {
            if (APP.api.key && !forceCheck) {
                return true;
            }

            const isLoggedIn = await API.checkLoginAndGetKey();

            if (!isLoggedIn && APP.api.key) {
                setStatus(STATUS.WARNING.class, MESSAGE.LOGIN_EXPIRED);
            }

            UI.updateState();

            return isLoggedIn;
        },

        checkLogoutFlag: () => {
            if (APP.storage.get('logout') === 'true') {
                APP.api.clearKey();
                APP.storage.remove('logout');
                setStatus(STATUS.WARNING.class, MESSAGE.LOGOUT);
            }
        },

        checkRecentLogin: async () => {
            const lastLoginCheck = APP.storage.get('loginCheck');
            if (lastLoginCheck && (Date.now() - parseInt(lastLoginCheck) < APP.auth.recentLoginGracePeriod)) {
                await API.checkLoginAndGetKey();
                APP.storage.remove('loginCheck');
            }
        },

        setupStorageListener: () => {
            window.addEventListener('storage', async event => {
                const { loginStatus, logout, guestToken } = APP.storage.keys;

                if (event.key === loginStatus && event.newValue === 'login_success') {
                    await API.checkLoginAndGetKey();
                    await Auth.updateUserInfo();
                    UI.updateState();
                    localStorage.removeItem(loginStatus);
                } else if (event.key === logout && event.newValue === 'true') {
                    APP.api.clearKey();
                    APP.user.clearInfo();
                    UI.updateState();
                    localStorage.removeItem(logout);
                } else if (event.key === guestToken && event.newValue) {
                    // 游客登录检测
                    console.log('[Y2K] Detected guest token change, syncing...');
                    await API.checkLoginAndGetKey();
                    await Auth.updateUserInfo();
                    UI.updateState();
                } else if (event.key === guestToken && !event.newValue) {
                    // 游客登出
                    APP.api.clearKey();
                    APP.user.clearInfo();
                    UI.updateState();
                }
            });
        },

        monitorLogout: () => {
            document.addEventListener('click', e => {
                const logoutButton = e.target.closest('#logoutBtn, .logout-btn');
                if (logoutButton || e.target.textContent?.match(/登出|注销|退出|logout|sign out/i)) {
                    APP.storage.set('logout', 'true');
                }
            });
        },

        startLoginStatusCheck: () => {
            const checkLoginInterval = setInterval(async () => {
                try {
                    const isLoggedIn = await API.checkLoginAndGetKey();

                    if (isLoggedIn) {
                        clearInterval(checkLoginInterval);

                        APP.storage.remove('loginStatus');
                        APP.storage.set('loginStatus', 'login_success');
                        APP.storage.set('loginCheck', Date.now().toString());
                    }
                } catch (error) { }
            }, APP.auth.loginCheckInterval);

            setTimeout(() => clearInterval(checkLoginInterval), APP.auth.loginCheckTimeout);
        },

        handleY2KSite: () => {
            console.log('[Y2K] Running on Y2K native site, forcing credentials sync...');

            // 立即检查并同步 localStorage 中的 guest_token 到 GM 存储
            const localStorageGuestToken = localStorage.getItem('guest_token') || '';
            if (localStorageGuestToken) {
                console.log('[Y2K] Found guest_token in localStorage, syncing to GM storage...');
                APP.guest.setToken(localStorageGuestToken);
            }

            // 记录初始状态(从 localStorage 读取,用于变化检测)
            let lastGuestToken = localStorageGuestToken;
            let hasCredentials = Boolean(APP.api.key);

            // 初始同步
            Auth.checkLoginIfNeeded(true).then(async success => {
                if (success) {
                    await Auth.updateUserInfo();
                    UI.updateState();
                    hasCredentials = true;
                    console.log('[Y2K] Initial credentials sync successful');
                }
            });

            // 持续监控登录状态变化(同页面内 storage 事件不触发)
            const syncTimer = setInterval(async () => {
                // 直接从 localStorage 读取当前 guest_token(同域内)
                const localStorageToken = localStorage.getItem('guest_token') || '';
                const currentGuestToken = APP.guest.getToken();

                // 检测游客 Token 变化(优先检测 localStorage 变化)
                if (localStorageToken !== lastGuestToken) {
                    console.log('[Y2K] Guest token changed in localStorage, syncing to GM...', {
                        had: Boolean(lastGuestToken),
                        has: Boolean(localStorageToken)
                    });
                    lastGuestToken = localStorageToken;

                    if (localStorageToken) {
                        // 新游客登录 - 同步到 GM 存储(跨域共享)
                        APP.guest.setToken(localStorageToken);
                        console.log('[Y2K] Guest token synced to GM storage');

                        const success = await Auth.checkLoginIfNeeded(true);
                        if (success) {
                            await Auth.updateUserInfo();
                            UI.updateState();
                            hasCredentials = true;
                        }
                    } else {
                        // 游客登出 - 清除 GM 存储
                        APP.guest.clearToken();
                        APP.api.clearKey();
                        APP.user.clearInfo();
                        UI.updateState();
                        hasCredentials = false;
                    }
                }

                // 检测普通用户登录
                if (!hasCredentials && !currentGuestToken) {
                    const success = await Auth.checkLoginIfNeeded(true);
                    if (success) {
                        await Auth.updateUserInfo();
                        UI.updateState();
                        hasCredentials = true;
                    }
                }
            }, 2000);

            setTimeout(() => clearInterval(syncTimer), 120000); // 2分钟后停止检查

            Auth.monitorLogout();
        },

        // 刷新认证信息
        refreshCredentials: async () => {
            console.log('[Y2K] Refreshing credentials...');
            setStatus(STATUS.INFO.class, MESSAGE.REFRESHING);

            // 清除当前凭据
            APP.api.clearKey();
            APP.user.clearInfo();

            // 重新获取
            const success = await API.checkLoginAndGetKey();

            if (success) {
                // 获取用户信息
                await Auth.updateUserInfo();
                console.log('[Y2K] Credentials refreshed successfully.');
            } else {
                // 尝试游客认证
                const guestToken = APP.guest.getToken();
                if (guestToken) {
                    await Auth.updateUserInfo();
                }
            }

            UI.updateState();
            return success;
        },

        // 更新用户信息
        updateUserInfo: async () => {
            const userInfo = await API.getUserInfo();
            if (userInfo) {
                APP.user.setInfo(userInfo.id, userInfo.identifier, userInfo.role);
                console.log('[Y2K] User info updated:', userInfo.identifier);
            } else {
                APP.user.clearInfo();
            }
        },

        // 启动账户变化监控
        startAccountMonitor: () => {
            if (Utils.isY2KSite()) return; // 不在 Y2K 网站运行

            let accountMismatch = false;

            setInterval(() => {
                const currentToken = API.getCurrentTokenUserId();
                const cachedUserId = APP.user.id;

                // 如果没有缓存用户ID但有当前Token,尝试同步
                if (!cachedUserId && currentToken) {
                    console.log('[Y2K] Detected new login, syncing...');
                    Auth.refreshCredentials();
                    return;
                }

                // 如果有缓存但Token变化,显示警告
                if (cachedUserId && currentToken && currentToken.id !== cachedUserId) {
                    if (!accountMismatch) {
                        console.warn('[Y2K] Account mismatch detected!', {
                            cached: cachedUserId,
                            current: currentToken.id
                        });
                        accountMismatch = true;
                        UI.showAccountMismatch();
                    }
                } else if (accountMismatch && (!currentToken || currentToken.id === cachedUserId)) {
                    accountMismatch = false;
                    UI.updateState();
                }
            }, APP.auth.accountCheckInterval);
        }
    };

    // --- UI & STYLES ---
    const STYLES = `
        /* MODAL SYSTEM */
        .y2k-modal-backdrop {
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            background: rgba(0, 0, 0, 0.6);
            backdrop-filter: blur(4px);
            z-index: 1000000;
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.3s;
        }
        .y2k-modal-backdrop.active {
            opacity: 1;
            pointer-events: auto;
        }
        .y2k-modal {
            background: rgba(20, 20, 20, 0.95);
            border: 1px solid rgba(255, 255, 255, 0.1);
            box-shadow: 0 10px 40px rgba(0,0,0,0.5);
            border-radius: 12px;
            width: 320px;
            padding: 24px;
            text-align: center;
            transform: scale(0.9);
            transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
            color: white;
            font-family: -apple-system, sans-serif;
            position: relative;
        }
        .y2k-modal-backdrop.active .y2k-modal {
            transform: scale(1);
        }
        .y2k-preview-img {
            max-width: 100%;
            max-height: 150px;
            border-radius: 8px;
            margin-bottom: 16px;
            object-fit: contain;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            display: none;
        }
        .y2k-preview-img.show {
            display: block;
        }
        .y2k-modal-title {
            font-size: 18px;
            font-weight: 600;
            margin-bottom: 8px;
        }
        .y2k-modal-text {
            font-size: 14px;
            color: #aaa;
            margin-bottom: 16px;
            word-break: break-all;
        }
        /* PROGRESS BAR */
        .y2k-progress-wrapper {
            height: 6px;
            background: rgba(255,255,255,0.1);
            border-radius: 3px;
            margin: 10px 0;
            overflow: hidden;
            display: none;
        }
        .y2k-progress-bar {
            height: 100%;
            background: linear-gradient(90deg, #1890ff, #52c41a);
            width: 0%;
            transition: width 0.2s;
        }
        /* STATUS PILL (Mini) */
        .y2k-mini-stat {
            position: fixed;
            bottom: 20px;
            left: 20px;
            background: rgba(20,20,20,0.9);
            color: #fff;
            padding: 8px 16px;
            border-radius: 20px;
            font-size: 12px;
            z-index: 999999;
            opacity: 1;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            pointer-events: auto;
            border: 1px solid rgba(255,255,255,0.1);
            box-shadow: 0 4px 12px rgba(0,0,0,0.5);
            display: flex;
            align-items: center;
            gap: 8px;
            backdrop-filter: blur(8px);
            animation: y2k-float 4s ease-in-out infinite;
        }
        @keyframes y2k-float {
            0%, 100% { transform: translateY(0); }
            50% { transform: translateY(-4px); }
        }
        .y2k-mini-stat.success {
            border-color: rgba(74, 222, 128, 0.4);
            color: #4ade80;
        }
        .y2k-mini-stat.error {
            border-color: rgba(239, 68, 68, 0.4);
            color: #ef4444;
        }
        .y2k-mini-stat.warning {
            border-color: rgba(230, 162, 60, 0.4);
            color: #e6a23c;
        }
        .y2k-mini-stat-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: currentColor;
            box-shadow: 0 0 8px currentColor;
            animation: y2k-breathe 2s ease-in-out infinite;
        }
        @keyframes y2k-breathe {
            0%, 100% { opacity: 1; transform: scale(1); box-shadow: 0 0 8px currentColor; }
            50% { opacity: 0.6; transform: scale(1.2); box-shadow: 0 0 12px currentColor; }
        }
        /* Scanline Effect via Pseudo-element */
        .y2k-mini-stat::after,
        .y2k-modal::after {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(
                to bottom,
                transparent,
                rgba(255, 255, 255, 0.05) 50%,
                transparent 50%
            );
            background-size: 100% 4px;
            pointer-events: none;
            opacity: 0.15;
            z-index: 1000;
            border-radius: inherit;
            animation: y2k-scan 8s linear infinite;
        }
        @keyframes y2k-scan {
            from { background-position: 0 0; }
            to { background-position: 0 100%; }
        }
        /* Refresh Button */
        .y2k-refresh-btn {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            background: rgba(255,255,255,0.1);
            border: 1px solid rgba(255,255,255,0.2);
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
            flex-shrink: 0;
        }
        .y2k-refresh-btn:hover {
            background: rgba(255,255,255,0.2);
            transform: rotate(180deg);
        }
        .y2k-refresh-btn.spinning {
            animation: y2k-spin 1s linear infinite;
        }
        .y2k-user-identifier {
            font-size: 10px;
            color: #888;
            margin-left: 4px;
            max-width: 80px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        /* Account Mismatch Warning */
        .y2k-mini-stat.mismatch {
            border-color: rgba(255, 165, 0, 0.6);
            color: #ffa500;
            animation: y2k-pulse 1.5s ease-in-out infinite;
        }
        @keyframes y2k-pulse {
            0%, 100% { box-shadow: 0 4px 12px rgba(255,165,0,0.3); }
            50% { box-shadow: 0 4px 20px rgba(255,165,0,0.6); }
        }
        /* Login Button */
        .y2k-login-btn {
            position: fixed;
            bottom: 65px;
            left: 20px;
            cursor: pointer;
            color: #e6a23c;
            font-size: 12px;
            background: rgba(230, 162, 60, 0.1);
            padding: 6px 16px;
            border-radius: 20px;
            border: 1px solid rgba(230, 162, 60, 0.4);
            backdrop-filter: blur(8px);
            z-index: 999999;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            gap: 6px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
        }
        .y2k-login-btn:hover {
            background: rgba(230, 162, 60, 0.2);
            transform: translateY(-2px);
        }
        .y2k-login-btn::before {
            content: '🔑';
            font-size: 10px;
        }
        /* BATCH UPLOAD LIST */
        .y2k-file-list {
            max-height: 200px;
            overflow-y: auto;
            margin: 12px 0;
            text-align: left;
        }
        .y2k-file-item {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 6px 8px;
            background: rgba(255,255,255,0.05);
            border-radius: 4px;
            margin-bottom: 6px;
            font-size: 12px;
        }
        .y2k-file-name {
            flex: 1;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .y2k-file-status {
            width: 14px;
            height: 14px;
            flex-shrink: 0;
        }
        .y2k-file-status.pending {
            width: 14px;
            height: 14px;
            border: 1px solid #666;
            border-radius: 50%;
        }
        .y2k-file-status.uploading {
            border: 2px solid #1890ff;
            border-top-color: transparent;
            border-radius: 50%;
            animation: y2k-spin 1s linear infinite;
        }
        .y2k-file-status.success {
            color: #4ade80;
        }
        .y2k-file-status.error {
            color: #ef4444;
        }
        @keyframes y2k-spin {
            to { transform: rotate(360deg); }
        }
        .y2k-summary {
            font-size: 11px;
            color: #aaa;
            margin-top: 8px;
        }
        .y2k-summary-text {
            color: #4ade80;
        }
        .y2k-summary-error {
            color: #ef4444;
        }
    `;

    function injectStyles() {
        if (document.getElementById('y2k-styles')) return;
        const style = document.createElement('style');
        style.id = 'y2k-styles';
        style.textContent = STYLES;
        document.head.appendChild(style);
    }

    // --- DOM ELEMENTS ---
    let modalBackdrop, modal, previewImg, modalTitle, modalText, progressBar, progressWrapper, fileListContainer, summaryText;
    let miniStat;

    const DOM = {
        statusElements: new Set(),
        loginButtons: new Set(),
        refreshButtons: new Set()
    };

    function setStatus(cls, msg, ttl = 0) {
        DOM.statusElements.forEach(el => {
            // 保存刷新按钮引用
            const refreshBtn = el.querySelector('.y2k-refresh-btn');

            el.className = `y2k-mini-stat ${cls}`;
            el.innerHTML = `<div class="y2k-mini-stat-dot"></div><span>${msg}</span>`;

            // 重新添加刷新按钮
            if (refreshBtn) {
                el.appendChild(refreshBtn);
            }
        });
        if (ttl && miniStat) return Utils.delay(ttl).then(UI.updateState);
    }

    const UI = {
        updateState: () => {
            const isLoggedIn = Boolean(APP.api.key) || Boolean(APP.guest.getToken());
            const userIdentifier = APP.user.identifier;

            DOM.loginButtons.forEach(btn => {
                btn.style.display = isLoggedIn ? 'none' : 'inline-block';
            });

            DOM.refreshButtons.forEach(btn => {
                btn.style.display = isLoggedIn ? 'flex' : 'none';
                btn.classList.remove('spinning');
            });

            DOM.statusElements.forEach(el => {
                // 保存刷新按钮引用
                const refreshBtn = el.querySelector('.y2k-refresh-btn');

                if (isLoggedIn) {
                    el.className = `y2k-mini-stat ${STATUS.SUCCESS.class}`;
                    const identifierHtml = userIdentifier
                        ? `<span class="y2k-user-identifier" title="${userIdentifier}">(${userIdentifier})</span>`
                        : '';
                    el.innerHTML = `<div class="y2k-mini-stat-dot"></div><span>${MESSAGE.READY}</span>${identifierHtml}`;
                } else {
                    el.className = 'y2k-mini-stat';
                    el.innerHTML = `<div class="y2k-mini-stat-dot" style="background:#666"></div><span>未录入凭据</span>`;
                }

                // 重新添加刷新按钮
                if (refreshBtn) {
                    el.appendChild(refreshBtn);
                }
            });
        },

        showAccountMismatch: () => {
            DOM.statusElements.forEach(el => {
                // 保存刷新按钮引用
                const refreshBtn = el.querySelector('.y2k-refresh-btn');

                el.className = `y2k-mini-stat ${STATUS.MISMATCH.class}`;
                el.innerHTML = `<div class="y2k-mini-stat-dot"></div><span>⚠️ ${MESSAGE.ACCOUNT_MISMATCH}</span><span class="y2k-user-identifier">请刷新</span>`;

                // 重新添加刷新按钮
                if (refreshBtn) {
                    el.appendChild(refreshBtn);
                }
            });
        },

        openLogin: () => {
            APP.storage.set('loginStatus', 'login_pending');
            window.open(APP.site.url, '_blank');
        }
    };

    function initUI() {
        console.log('[Y2K] Initializing UI elements...');
        injectStyles();

        // 1. Mini Status Indicator
        miniStat = document.createElement('div');
        miniStat.className = 'y2k-mini-stat';
        miniStat.innerHTML = '<div class="y2k-mini-stat-dot"></div><span>Initializing...</span>';
        document.body.appendChild(miniStat);
        DOM.statusElements.add(miniStat);

        // 2. Login Button (for toolbar injection)
        const loginBtn = document.createElement('div');
        loginBtn.className = 'y2k-login-btn';
        loginBtn.textContent = '点击登录Y2K';
        loginBtn.addEventListener('click', UI.openLogin);
        loginBtn.style.display = 'none';
        document.body.appendChild(loginBtn);
        DOM.loginButtons.add(loginBtn);

        // 3. Refresh Button
        const refreshBtn = document.createElement('div');
        refreshBtn.className = 'y2k-refresh-btn';
        refreshBtn.innerHTML = '🔄';
        refreshBtn.title = '刷新认证';
        refreshBtn.style.display = 'none';
        refreshBtn.addEventListener('click', async () => {
            refreshBtn.classList.add('spinning');
            await Auth.refreshCredentials();
            refreshBtn.classList.remove('spinning');
        });
        miniStat.appendChild(refreshBtn);
        DOM.refreshButtons.add(refreshBtn);

        // 3. Modal Structure
        modalBackdrop = document.createElement('div');
        modalBackdrop.className = 'y2k-modal-backdrop';

        modalBackdrop.innerHTML = `
            <div class="y2k-modal">
                <img class="y2k-preview-img" id="y2k-preview">
                <div class="y2k-modal-title" id="y2k-title">Uploading...</div>
                <div class="y2k-modal-text" id="y2k-text">Please wait</div>
                <div class="y2k-progress-wrapper" id="y2k-progress-wrap">
                    <div class="y2k-progress-bar" id="y2k-progress"></div>
                </div>
                <div class="y2k-file-list" id="y2k-file-list"></div>
                <div class="y2k-summary" id="y2k-summary"></div>
            </div>
        `;
        document.body.appendChild(modalBackdrop);

        // Bind Elements
        modal = modalBackdrop.querySelector('.y2k-modal');
        previewImg = modalBackdrop.querySelector('#y2k-preview');
        modalTitle = modalBackdrop.querySelector('#y2k-title');
        modalText = modalBackdrop.querySelector('#y2k-text');
        progressWrapper = modalBackdrop.querySelector('#y2k-progress-wrap');
        progressBar = modalBackdrop.querySelector('#y2k-progress');
        fileListContainer = modalBackdrop.querySelector('#y2k-file-list');
        summaryText = modalBackdrop.querySelector('#y2k-summary');

        // Close on backdrop click
        modalBackdrop.addEventListener('click', (e) => {
            if (e.target === modalBackdrop) hideModal();
        });
    }

    // --- MODAL CONTROLS ---
    function showModal(title, text, showProgress = false, imgSrc = null, showFileList = false) {
        clearTimeout(autoCloseTimer);
        modalBackdrop.classList.add('active');

        modalTitle.textContent = title;
        modalText.innerHTML = text;

        if (imgSrc) {
            previewImg.src = imgSrc;
            previewImg.classList.add('show');
        } else {
            previewImg.classList.remove('show');
            previewImg.src = '';
        }

        if (showFileList) {
            progressWrapper.style.display = 'none';
            previewImg.classList.remove('show');
            fileListContainer.style.display = 'block';
        } else if (showProgress) {
            progressWrapper.style.display = 'block';
            progressBar.style.width = '0%';
            fileListContainer.style.display = 'none';
        } else {
            progressWrapper.style.display = 'none';
            fileListContainer.style.display = 'none';
        }
    }

    function updateProgress(percent) {
        progressBar.style.width = `${percent}%`;
    }

    // Update file list display
    function updateFileList(files) {
        fileListContainer.innerHTML = '';
        files.forEach((file, index) => {
            const item = document.createElement('div');
            item.className = 'y2k-file-item';

            let statusHtml = '';
            if (file.status === 'pending') {
                statusHtml = '<div class="y2k-file-status pending"></div>';
            } else if (file.status === 'uploading') {
                statusHtml = '<div class="y2k-file-status uploading"></div>';
            } else if (file.status === 'success') {
                statusHtml = '<svg class="y2k-file-status success" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 17"></polyline><path d="m1 12 4-4 4 4 16"></path></svg>';
            } else if (file.status === 'error') {
                statusHtml = '<svg class="y2k-file-status error" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>';
            }

            item.innerHTML = `
                ${statusHtml}
                <div class="y2k-file-name" title="${file.file.name}">${file.file.name}</div>
                <div class="y2k-file-size">${formatFileSize(file.file.size)}</div>
            `;
            fileListContainer.appendChild(item);
        });
    }

    function updateSummary(summary) {
        let html = '';
        if (summary.uploaded > 0) {
            html += `<span class="y2k-summary-text">✓ ${summary.uploaded} 上传成功</span>`;
        }
        if (summary.failed > 0) {
            html += ` <span class="y2k-summary-error">✗ ${summary.failed} 失败</span>`;
        }
        if (summary.copied > 0) {
            html += ` | 已复制 ${summary.copied} 个链接`;
        }
        summaryText.innerHTML = html;
    }

    function formatFileSize(bytes) {
        if (bytes < 1024) return bytes + ' B';
        if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
        return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
    }

    function hideModal() {
        modalBackdrop.classList.remove('active');
    }

    function hideModalDelayed(delay = 1500) {
        clearTimeout(autoCloseTimer);
        autoCloseTimer = setTimeout(() => {
            hideModal();
        }, delay);
    }

    // --- LOGIC ---

    function toggleMode() {
        console.log('[Y2K] Toggling mode. Previous state:', uploadMode);
        uploadMode = !uploadMode;
        if (uploadMode) {
            setStatus(STATUS.SUCCESS.class, 'Y2K已就绪');
        } else {
            UI.updateState();
            hideModal();
        }
    }

    // Validates files and calls upload
    function handleFiles(files) {
        if (!files || !files.length) return;

        const validFiles = Array.from(files).filter(file => file.type.indexOf('image') !== -1);
        if (validFiles.length === 0) return;

        // Add to pending files with initial status
        pendingFiles = validFiles.map(file => ({
            file,
            status: 'pending'
        }));

        // Show modal with file list
        showModal('批量上传', `准备上传 ${pendingFiles.length} 个文件`, false, null, true);
        updateFileList(pendingFiles);

        // Start batch upload
        uploadBatchImages(pendingFiles);
    }

    async function uploadBatchImages(files) {
        // 检查 API Key
        if (!APP.api.key || !(await Auth.checkLoginIfNeeded())) {
            showModal('需要认证', '请先登录 Y2K (点击页面左下角按钮)', false, null, false);
            return;
        }

        // Mark all as uploading
        files.forEach(f => f.status = 'uploading');
        updateFileList(files);

        const formData = new FormData();
        files.forEach(f => {
            formData.append('images', f.file);
        });

        // 构建请求头 - 游客使用 Authorization,普通用户使用 X-API-Key
        const headers = {};
        if (APP.api.key.startsWith('guest:')) {
            // 游客模式:提取 guest token 使用 Authorization
            const guestToken = APP.api.key.replace('guest:', '');
            headers['Authorization'] = `Bearer ${guestToken}`;
        } else {
            // 普通用户模式:使用 X-API-Key
            headers['X-API-Key'] = APP.api.key;
        }

        GM_xmlhttpRequest({
            method: 'POST',
            url: APP.api.endpoints.batchUpload,
            headers,
            data: formData,
            upload: {
                onprogress: (e) => {
                    if (e.lengthComputable) {
                        const percent = Math.round((e.loaded / e.total) * 100);
                        modalText.textContent = `上传中... ${percent}%`;
                    }
                }
            },
            onload: function (response) {
                try {
                    const res = JSON.parse(response.responseText);
                    if (res.success) {
                        // Process results
                        const results = res.data || [];
                        const errors = res.errors || [];
                        const summary = res.summary || {};

                        // Update file statuses
                        files.forEach((f, index) => {
                            const result = results.find(r => r.data.name === f.file.name);
                            const error = errors.find(e => e.filename === f.file.name);

                            if (error) {
                                f.status = 'error';
                            } else if (result) {
                                f.status = 'success';
                                f.url = APP.site.url + result.data.url;
                            } else {
                                f.status = 'error';
                            }
                        });

                        updateFileList(files);

                        // Copy all successful URLs to clipboard
                        const successful = files.filter(f => f.status === 'success');
                        const markdownLinks = successful.map(f => `![${f.file.name}](${f.url})`).join('\n');
                        GM_setClipboard(markdownLinks);

                        // Insert into editor if available (always insert for batch)
                        if (successful.length >= 1) {
                            insertToEditor(markdownLinks);
                        }

                        // Show summary
                        updateSummary({
                            uploaded: summary.uploaded || 0,
                            failed: summary.failed || 0,
                            copied: successful.length
                        });

                        // Auto-close after success
                        hideModalDelayed(4000);
                    } else {
                        if (response.status === 401) {
                            showModal('认证失败', 'API Key 无效或已过期。请重新登录 Y2K。', false, null, false);
                            APP.api.clearKey();
                            UI.updateState();
                        } else {
                            showModal('错误', res.error || '批量上传失败', false, null, false);
                        }
                    }
                } catch (e) {
                    if (response.status === 401) {
                        showModal('认证失败', 'API Key 无效。请重新登录 Y2K。', false, null, false);
                        APP.api.clearKey();
                        UI.updateState();
                    } else {
                        showModal('错误', '服务器响应无效', false, null, false);
                    }
                }
            },
            onerror: function (err) {
                showModal('网络错误', '请检查控制台', false, null, false);
                files.forEach(f => f.status = 'error');
                updateFileList(files);
            }
        });
    }

    function insertToEditor(md) {
        const cm = document.querySelector('.CodeMirror')?.CodeMirror;
        if (cm) {
            cm.replaceRange(`\n${md}\n`, cm.getCursor());
            return;
        }
        const activeEl = document.activeElement;
        if (activeEl && (activeEl.tagName === 'TEXTAREA' || activeEl.isContentEditable)) {
            if (activeEl.tagName === 'TEXTAREA') {
                const val = activeEl.value;
                const start = activeEl.selectionStart;
                const end = activeEl.selectionEnd;
                activeEl.value = val.substring(0, start) + `\n${md}\n` + val.substring(end);
            } else {
                document.execCommand('insertText', false, `\n${md}\n`);
            }
        }
    }

    // ===== 初始化 (Initialization) =====
    const init = async () => {
        // 初始化 UI(确保 DOM 已准备好)
        console.log('[Y2K] Initializing...');
        initUI();

        // (已取消 Alt+U 切换,默认有凭证即开启)

        // 如果在 Y2K 网站,则执行特定的登录/登出辅助逻辑
        if (Utils.isY2KSite()) {
            Auth.handleY2KSite();
        } else {
            // 在其他网站上的核心初始化流程
            // 页面重新获得焦点时,完整检查登录状态(包括游客账户)
            window.addEventListener('focus', async () => {
                console.log('[Y2K] Page focused, checking credentials...');
                const hadCredentials = Boolean(APP.api.key);
                const success = await Auth.checkLoginIfNeeded(true); // 强制检查

                // 如果登录状态变化,更新用户信息
                if (success !== hadCredentials || success) {
                    await Auth.updateUserInfo();
                    UI.updateState();
                }
            });

            // 监听粘贴和拖拽事件
            // Paste
            document.addEventListener('paste', (event) => {
                if (!uploadMode) return;
                const items = (event.clipboardData || event.originalEvent.clipboardData).items;
                const files = [];
                for (let i = 0; i < items.length; i++) {
                    if (items[i].type.indexOf('image') !== -1) {
                        event.preventDefault();
                        const file = items[i].getAsFile();
                        if (file) files.push(file);
                    }
                }
                if (files.length > 0) {
                    handleFiles(files);
                }
            });

            // Drag Drop
            const handleDragEvent = (e) => {
                if (!uploadMode) return;

                // Check if it's a file drag
                const isFile = e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files');
                if (!isFile) return;

                e.preventDefault();
                e.stopImmediatePropagation();

                if (e.type === 'dragover') {
                    e.dataTransfer.dropEffect = 'copy';
                }

                if (e.type === 'drop') {
                    console.log('[Y2K] File dropped successfully.');
                    if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
                        handleFiles(e.dataTransfer.files);
                    }
                }
            };

            document.addEventListener('dragenter', handleDragEvent, true);
            document.addEventListener('dragover', handleDragEvent, true);
            document.addEventListener('drop', handleDragEvent, true);
        }

        // 启动时执行认证状态检查(所有网站都执行)
        Auth.checkLogoutFlag();
        Auth.setupStorageListener();
        await Auth.checkRecentLogin();
        await Auth.checkLoginIfNeeded();

        // 获取并缓存用户信息
        await Auth.updateUserInfo();

        // 启动账户变化监控(仅在非Y2K网站)
        Auth.startAccountMonitor();

        // 更新 UI 状态
        UI.updateState();
        console.log('[Y2K] Initialization complete.');
    };

    // 使用 DOMContentLoaded 而不是 load,确保更早执行
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        // DOM 已经加载完成,直接执行
        init();
    }

})();