Pixiv AI Tag

对Pixiv中的AI生成图像添加一个标注

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @license MIT
// @name        Pixiv AI Tag
// @description 对Pixiv中的AI生成图像添加一个标注
// @author      BAKAOLC
// @version     1.0.0
// @icon        http://www.pixiv.net/favicon.ico
// @match       *://www.pixiv.net/*
// @namespace   none
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.deleteValue
// @grant       GM.listValues
// @grant       GM.xmlHttpRequest
// @supportURL  https://github.com/BAKAOLC/Tampermonkey-Script
// @homepageURL https://github.com/BAKAOLC/Tampermonkey-Script
// @noframes
// ==/UserScript==

(function () {
    'use strict';


    // ============= 配置常量 =============
    const CONFIG = {
        QUERY_INTERVAL: 500, // 查询间隔(毫秒)
        LOG_LEVEL: 'info', // 日志级别: 'debug', 'info', 'warn', 'error'

        // 缓存配置
        CACHE: {
            ILLUST_EXPIRE_TIME: 60 * 60 * 1000,      // 插画缓存1小时
            CLEANUP_INTERVAL: 10 * 60 * 1000,        // 清理间隔10分钟
            MAX_ENTRIES: 1000                        // 最大缓存条目数
        },

        // 跨标签页同步配置
        CROSS_TAB_SYNC: {
            LOCK_EXPIRE_TIME: 15 * 1000,           // 锁过期时间15秒
            REQUEST_INTERVAL: 1000,                 // 跨标签页请求间隔1秒
            HEARTBEAT_INTERVAL: 3 * 1000,           // 心跳间隔3秒
            HEARTBEAT_EXPIRE_TIME: 10 * 1000        // 心跳过期时间10秒
        },

        // 速率限制配置
        RATE_LIMIT: {
            INITIAL_DELAY: 5000,     // 初始重试延迟5秒
            MAX_DELAY: 60000,        // 最大重试延迟1分钟
            BACKOFF_MULTIPLIER: 2    // 退避倍数
        },

        // AI标签列表
        AI_TAGS: [
            'AI', 'AI-generated', 'AI绘画', 'AI絵', 'AI生成', 'AI生成作品', 'AI作成',
            'AIartwork', 'AIgenerated', 'AIアート', 'AIイラスト', 'AIのべりすと',
            'NovelAI', 'StableDiffusion', 'MidJourney', 'DALL-E', 'Diffusion',
            'stable_diffusion', 'novel_ai', 'midjourney', 'dall_e'
        ],

        // 用户配置(可通过脚本修改)
        USER_CONFIG: {
            query_delay: 0,        // 查询间隔,时间单位为毫秒,0代表无延时
            remove_image: 0,       // 是否移除AI作品的预览图 0:不移除 1:仅屏蔽图像显示 2:从网页中移除
            show_ai_possible: true, // 是否显示可能是AI的标签
            enable_tag_detection: true, // 是否启用标签检测
            enable_auto_cache: true     // 是否启用自动缓存
        }
    };

    // 页面选择器配置 - 使用更通用的匹配方式
    const SELECTORS = {
        // 通用选择器:所有包含图像且链接到artwork的a标签
        ARTWORK_LINKS: 'a[href*="/artworks/"]:not(.add_ai_tag)',

        // 图像容器选择器:用于查找包含图像的链接
        IMAGE_CONTAINERS: [
            'a[href*="/artworks/"] img',           // 直接包含图像的链接
            'a[href*="/artworks/"] canvas',        // 包含canvas的链接
            'a[href*="/artworks/"] svg',           // 包含svg的链接
            'a[href*="/artworks/"] [style*="background-image"]' // 背景图像
        ],

        // 用于移除图像的父级深度配置(保留原有逻辑)
        REMOVE_PARENT_DEPTH: 4
    };

    // ============= 工具函数 =============
    const Utils = {
        sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        },

        // 等待DOM元素出现
        waitForElement(selector, timeout = 10000, interval = 100) {
            return new Promise((resolve, reject) => {
                const startTime = Date.now();

                const check = () => {
                    const element = document.querySelector(selector);
                    if (element) {
                        resolve(element);
                        return;
                    }

                    if (Date.now() - startTime >= timeout) {
                        reject(new Error(`Timeout waiting for element: ${selector}`));
                        return;
                    }

                    setTimeout(check, interval);
                };

                check();
            });
        },

        // 等待页面数据加载完成
        waitForPageData(illustId, timeout = 15000) {
            return new Promise((resolve, reject) => {
                const startTime = Date.now();

                const check = () => {
                    // 检查多种数据源
                    const conditions = [
                        // 检查preload-data脚本
                        () => {
                            const scripts = document.querySelectorAll('script');
                            for (const script of scripts) {
                                if (script.textContent && script.textContent.includes('preload-data')) {
                                    const patterns = [
                                        /{"timestamp".*?}(?=<\/script>)/,
                                        /{"timestamp"[^}]*}[^{]*{[^}]*"illust"[^}]*}/,
                                        /{[^}]*"illust"[^}]*}/
                                    ];

                                    for (const pattern of patterns) {
                                        const match = script.textContent.match(pattern);
                                        if (match) {
                                            try {
                                                const data = JSON.parse(match[0]);
                                                if (data.illust?.[illustId]) {
                                                    return { type: 'preload-data', data: data };
                                                }
                                            } catch (e) {
                                                // 继续尝试
                                            }
                                        }
                                    }
                                }
                            }
                            return null;
                        },

                        // 检查全局变量
                        () => {
                            const globalVars = ['__INITIAL_STATE__', '__PRELOADED_STATE__', 'pixiv'];
                            for (const varName of globalVars) {
                                if (window[varName]) {
                                    const data = window[varName];
                                    const illust = data.illust?.[illustId] || data.preload?.illust?.[illustId];
                                    if (illust) {
                                        return { type: 'global', data: data, varName: varName };
                                    }
                                }
                            }
                            return null;
                        }
                    ];

                    for (const condition of conditions) {
                        try {
                            const result = condition();
                            if (result) {
                                resolve(result);
                                return;
                            }
                        } catch (e) {
                            // 忽略错误,继续检查
                        }
                    }

                    if (Date.now() - startTime >= timeout) {
                        reject(new Error(`Timeout waiting for page data for illust ${illustId}`));
                        return;
                    }

                    setTimeout(check, 200);
                };

                check();
            });
        },

        log(message, level = 'info') {
            const levels = { debug: 0, info: 1, warn: 2, error: 3 };
            const configLevel = levels[CONFIG.LOG_LEVEL] !== undefined ? levels[CONFIG.LOG_LEVEL] : 1;
            const messageLevel = levels[level] !== undefined ? levels[level] : 1;

            // 只输出等于或高于配置级别的日志
            if (messageLevel < configLevel) return;

            const prefix = '[Pixiv AI Tag]';
            const timestamp = new Date().toLocaleTimeString();
            switch (level) {
                case 'error':
                    console.error(`${prefix} [${timestamp}] ${message}`);
                    break;
                case 'warn':
                    console.warn(`${prefix} [${timestamp}] ${message}`);
                    break;
                case 'debug':
                    console.debug(`${prefix} [${timestamp}] ${message}`);
                    break;
                default:
                    console.log(`${prefix} [${timestamp}] ${message}`);
            }
        },

        safeQuerySelector(selector, context = document) {
            try {
                return context.querySelector(selector);
            } catch (error) {
                this.log(`Invalid selector: ${selector}`, 'error');
                return null;
            }
        },

        safeQuerySelectorAll(selector, context = document) {
            try {
                return context.querySelectorAll(selector);
            } catch (error) {
                this.log(`Invalid selector: ${selector}`, 'error');
                return [];
            }
        },

        // 检查标签中是否包含AI相关标签
        checkAITags(tags) {
            if (!tags || !Array.isArray(tags)) return false;

            const tagStrings = tags.map(tag => {
                if (typeof tag === 'string') {
                    return tag;
                } else if (tag && typeof tag === 'object' && tag.tag) {
                    return tag.tag;
                }
                return '';
            }).filter(tag => tag.length > 0);

            // 检查是否有任何标签匹配AI标签列表
            for (const aiTag of CONFIG.AI_TAGS) {
                for (const tagString of tagStrings) {
                    const lowerTag = tagString.toLowerCase();
                    const lowerAiTag = aiTag.toLowerCase();

                    // 精确匹配或者作为独立单词匹配
                    if (lowerTag === lowerAiTag ||
                        lowerTag.includes(`_${lowerAiTag}_`) ||
                        lowerTag.startsWith(`${lowerAiTag}_`) ||
                        lowerTag.endsWith(`_${lowerAiTag}`) ||
                        (lowerAiTag.length >= 3 && lowerTag.includes(lowerAiTag) &&
                            !lowerTag.match(new RegExp(`[a-z]${lowerAiTag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[a-z]`)))) {
                        Utils.log(`Found AI tag match: "${tagString}" matches "${aiTag}"`, 'debug');
                        return true;
                    }
                }
            }

            return false;
        },

        // 获取父节点
        getParentNodeWithDepth(node, depth) {
            while (depth > 0) {
                if (node.parentNode)
                    node = node.parentNode;
                else
                    return null;
                depth--;
            }
            return node;
        }
    };

    // ============= 跨标签页缓存管理器 =============
    class CrossTabCacheManager {
        constructor() {
            this.cachePrefix = 'pixiv_ai_cache_';
            this.lastCleanup = 0;
            this.initializeCleanup();
        }

        getCacheKey(type, id) {
            return `${this.cachePrefix}${type}_${id}`;
        }

        async getCache(type, id) {
            try {
                const key = this.getCacheKey(type, id);
                const data = await GM.getValue(key, null);

                if (!data) return null;

                const parsed = JSON.parse(data);
                const now = Date.now();

                if (now - parsed.timestamp > CONFIG.CACHE.ILLUST_EXPIRE_TIME) {
                    await GM.deleteValue(key);
                    return null;
                }

                Utils.log(`Cache hit for ${type}:${id}`, 'debug');
                return parsed.data;
            } catch (error) {
                Utils.log(`Cache get error for ${type}:${id}: ${error.message}`, 'error');
                return null;
            }
        }

        async setCache(type, id, data) {
            try {
                const key = this.getCacheKey(type, id);
                const cacheData = {
                    data: data,
                    timestamp: Date.now(),
                    type: type,
                    id: id
                };

                await GM.setValue(key, JSON.stringify(cacheData));
                Utils.log(`Cache set for ${type}:${id}`, 'debug');
                this.scheduleCleanup();
            } catch (error) {
                Utils.log(`Cache set error for ${type}:${id}: ${error.message}`, 'error');
            }
        }

        initializeCleanup() {
            this.scheduleCleanup();
        }

        scheduleCleanup() {
            const now = Date.now();
            if (now - this.lastCleanup > CONFIG.CACHE.CLEANUP_INTERVAL) {
                setTimeout(() => this.cleanupExpiredCache(), 1000);
            }
        }

        async cleanupExpiredCache() {
            try {
                const now = Date.now();
                this.lastCleanup = now;

                const keys = await GM.listValues();
                const cacheKeys = keys.filter(key => key.startsWith(this.cachePrefix));
                let cleanedCount = 0;

                for (const key of cacheKeys) {
                    try {
                        const data = await GM.getValue(key, null);
                        if (!data) continue;

                        const parsed = JSON.parse(data);
                        if (now - parsed.timestamp > CONFIG.CACHE.ILLUST_EXPIRE_TIME) {
                            await GM.deleteValue(key);
                            cleanedCount++;
                        }
                    } catch (error) {
                        await GM.deleteValue(key);
                        cleanedCount++;
                    }
                }

                if (cleanedCount > 0) {
                    Utils.log(`Cleaned up ${cleanedCount} expired cache entries`, 'debug');
                }
            } catch (error) {
                Utils.log(`Cache cleanup error: ${error.message}`, 'error');
            }
        }
    }

    // ============= 跨标签页同步管理器 =============
    class CrossTabSyncManager {
        constructor() {
            this.tabId = this.generateTabId();
            this.lockKey = 'pixiv_ai_request_lock';
            this.lastRequestKey = 'pixiv_ai_last_request';
            this.heartbeatKey = 'pixiv_ai_heartbeat';
            this.heartbeatInterval = null;

            this.cleanupExpiredLocks();
            this.startHeartbeat();
            this.setupCleanupOnUnload();
        }

        generateTabId() {
            return `tab_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
        }

        startHeartbeat() {
            this.updateHeartbeat();
            this.heartbeatInterval = setInterval(async () => {
                await this.updateHeartbeat();
                await this.checkDeadTabs();
            }, CONFIG.CROSS_TAB_SYNC.HEARTBEAT_INTERVAL);
        }

        async updateHeartbeat() {
            try {
                const heartbeatData = await GM.getValue(this.heartbeatKey, '{}');
                const heartbeats = JSON.parse(heartbeatData);
                heartbeats[this.tabId] = { timestamp: Date.now(), isActive: true };
                await GM.setValue(this.heartbeatKey, JSON.stringify(heartbeats));
            } catch (error) {
                Utils.log(`Error updating heartbeat: ${error.message}`, 'error');
            }
        }

        async executeRequestSynchronized(requestFunction, requestData) {
            const maxRetries = 3;
            let retries = 0;

            while (retries < maxRetries) {
                try {
                    const lockAcquired = await this.acquireLock();

                    if (!lockAcquired) {
                        await Utils.sleep(200);
                        retries++;
                        continue;
                    }

                    try {
                        await this.shouldWaitForOtherTabs();
                        const result = await requestFunction(requestData);
                        await this.recordRequestTime();
                        return result;
                    } finally {
                        await this.releaseLock();
                    }
                } catch (error) {
                    await this.releaseLock();
                    retries++;
                    if (retries >= maxRetries) {
                        throw error;
                    }
                    await Utils.sleep(1000 * retries);
                }
            }

            throw new Error('Failed to execute synchronized request after retries');
        }

        async acquireLock() {
            try {
                const now = Date.now();
                const lockData = await GM.getValue(this.lockKey, null);

                if (lockData) {
                    const lock = JSON.parse(lockData);
                    if (lock.tabId === this.tabId) {
                        lock.timestamp = now;
                        await GM.setValue(this.lockKey, JSON.stringify(lock));
                        return true;
                    }

                    if (now - lock.timestamp < CONFIG.CROSS_TAB_SYNC.LOCK_EXPIRE_TIME) {
                        return false;
                    } else {
                        await GM.deleteValue(this.lockKey);
                    }
                }

                const newLock = { tabId: this.tabId, timestamp: now };
                await GM.setValue(this.lockKey, JSON.stringify(newLock));
                return true;
            } catch (error) {
                Utils.log(`Error acquiring lock: ${error.message}`, 'error');
                return false;
            }
        }

        async releaseLock() {
            try {
                const lockData = await GM.getValue(this.lockKey, null);
                if (lockData) {
                    const lock = JSON.parse(lockData);
                    if (lock.tabId === this.tabId) {
                        await GM.deleteValue(this.lockKey);
                    }
                }
            } catch (error) {
                Utils.log(`Error releasing lock: ${error.message}`, 'error');
            }
        }

        async shouldWaitForOtherTabs() {
            try {
                const lastRequestTime = await GM.getValue(this.lastRequestKey, 0);
                const now = Date.now();
                const timeSinceLastRequest = now - lastRequestTime;

                if (timeSinceLastRequest < CONFIG.CROSS_TAB_SYNC.REQUEST_INTERVAL) {
                    const waitTime = CONFIG.CROSS_TAB_SYNC.REQUEST_INTERVAL - timeSinceLastRequest;
                    await Utils.sleep(waitTime);
                }
            } catch (error) {
                Utils.log(`Error checking request interval: ${error.message}`, 'error');
            }
        }

        async recordRequestTime() {
            try {
                await GM.setValue(this.lastRequestKey, Date.now());
            } catch (error) {
                Utils.log(`Error recording request time: ${error.message}`, 'error');
            }
        }

        async cleanupExpiredLocks() {
            try {
                const now = Date.now();
                const lockData = await GM.getValue(this.lockKey, null);

                if (lockData) {
                    const lock = JSON.parse(lockData);
                    if (now - lock.timestamp > CONFIG.CROSS_TAB_SYNC.LOCK_EXPIRE_TIME) {
                        await GM.deleteValue(this.lockKey);
                    }
                }
            } catch (error) {
                Utils.log(`Error cleaning up locks: ${error.message}`, 'error');
            }
        }

        setupCleanupOnUnload() {
            const cleanup = async () => {
                try {
                    await this.releaseLock();
                    if (this.heartbeatInterval) {
                        clearInterval(this.heartbeatInterval);
                    }
                } catch (error) {
                    // 忽略错误
                }
            };

            window.addEventListener('beforeunload', cleanup);
            window.addEventListener('unload', cleanup);
        }

        async checkDeadTabs() {
            // 简化版本,只清理过期锁
            await this.cleanupExpiredLocks();
        }
    }

    // ============= API 客户端 =============
    class APIClient {
        constructor(syncManager) {
            this.syncManager = syncManager;
        }

        async fetchPixivIllust(id) {
            const requestFunction = async () => {
                return new Promise((resolve, reject) => {
                    GM.xmlHttpRequest({
                        method: 'GET',
                        url: `https://www.pixiv.net/ajax/illust/${id}`,
                        headers: {
                            'Accept': 'application/json',
                            'Referer': 'https://www.pixiv.net/'
                        },
                        onload: function (response) {
                            if (response.status >= 200 && response.status < 300) {
                                try {
                                    const data = JSON.parse(response.responseText);
                                    resolve({ json: () => Promise.resolve(data), ok: true });
                                } catch (error) {
                                    reject(new Error(`JSON parse error: ${error.message}`));
                                }
                            } else {
                                reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
                            }
                        },
                        onerror: function (error) {
                            reject(new Error(`Network error: ${error.message || 'Unknown error'}`));
                        },
                        ontimeout: function () {
                            reject(new Error('Request timeout'));
                        },
                        timeout: 10000
                    });
                });
            };

            return await this.syncManager.executeRequestSynchronized(requestFunction, { id });
        }
    }

    // ============= Pixiv页面自动记录器 =============
    class PixivAutoRecorder {
        constructor(cacheManager) {
            this.cacheManager = cacheManager;
            this.isPixivPage = location.hostname === 'www.pixiv.net';
        }

        async initialize() {
            if (!this.isPixivPage || !CONFIG.USER_CONFIG.enable_auto_cache) {
                Utils.log('Pixiv auto recorder skipped (not pixiv page or disabled)', 'debug');
                return;
            }

            Utils.log('Initializing Pixiv auto recorder...', 'debug');
            try {
                await this.recordCurrentPage();
                Utils.log('Pixiv auto recorder initialized successfully', 'debug');
            } catch (error) {
                Utils.log(`Pixiv auto recorder error: ${error.message}`, 'error');
                // 不要抛出错误,让脚本继续运行
            }
        }

        async recordCurrentPage() {
            try {
                const url = location.href;

                if (url.includes('/artworks/')) {
                    await this.recordIllustPage();
                }
            } catch (error) {
                Utils.log(`Error recording current page: ${error.message}`, 'error');
            }
        }

        async recordIllustPage() {
            try {
                const match = location.href.match(/\/artworks\/(\d+)/);
                if (!match) return;

                const illustId = match[1];
                Utils.log(`Recording illust page: ${illustId}`, 'debug');

                let pageData = null;
                try {
                    pageData = await Utils.waitForPageData(illustId, 10000);
                } catch (error) {
                    Utils.log(`Timeout waiting for page data: ${error.message}`, 'warn');
                }

                if (pageData && pageData.type !== 'basic') {
                    let illust = null;

                    if (pageData.type === 'preload-data') {
                        illust = pageData.data.illust?.[illustId];
                    } else if (pageData.type === 'global') {
                        illust = pageData.data.illust?.[illustId] || pageData.data.preload?.illust?.[illustId];
                    }

                    if (illust) {
                        await this.processIllustData(illustId, illust);
                    }
                }
            } catch (error) {
                Utils.log(`Error recording illust page: ${error.message}`, 'error');
            }
        }

        async processIllustData(illustId, illust) {
            try {
                const tags = illust.tags?.tags || [];
                const isAIByType = illust.aiType === 2;
                const isAIPossibleByType = illust.aiType >= 2;
                const isAIByTags = CONFIG.USER_CONFIG.enable_tag_detection ? Utils.checkAITags(tags) : false;

                const cacheData = {
                    ai: isAIByType || isAIByTags,
                    ai_is_possible: isAIPossibleByType || isAIByTags,
                    user_id: illust.userId,
                    title: illust.title,
                    tags: tags,
                    aiType: illust.aiType,
                    isAIByTags: isAIByTags
                };

                await this.cacheManager.setCache('pixiv_illust', illustId, cacheData);
                Utils.log(`Auto-recorded illust ${illustId} (AI: ${cacheData.ai})`, 'debug');
            } catch (error) {
                Utils.log(`Error processing illust data: ${error.message}`, 'error');
            }
        }
    }

    // ============= DOM 操作工具 =============
    class DOMUtils {
        static addStyles() {
            if (document.getElementById('pixiv-ai-tag-styles')) return;

            const styles = `
.add_ai_tag_view {
    padding: 0px 6px;
    border-radius: 3px;
    color: rgb(255, 255, 255);
    background: rgb(96, 64, 255);
    font-weight: bold;
    font-size: 10px;
    line-height: 16px;
    user-select: none;
}

                .add_ai_possible_tag_view {
                    background: rgb(96, 64, 127);
                }

                .add_ai_tag_view.ai-by-tags {
                    background: rgb(255, 96, 64);
                }

                .add_ai_possible_tag_view.ai-by-tags {
                    background: rgb(127, 64, 96);
                }
            `;

            const styleElement = document.createElement('style');
            styleElement.id = 'pixiv-ai-tag-styles';
            styleElement.textContent = styles;
            document.head.appendChild(styleElement);
        }

        static addTag(node, text, className = 'add_ai_tag_view', isAIByTags = false) {
            // 只对包含图片的链接添加标签
            const img = node.querySelector('img');
            if (!img) {
                return false; // 不是图片链接,跳过
            }

            // 检查是否已经有AI标签,避免重复添加
            if (node.querySelector('.add_ai_tag_view, .add_ai_possible_tag_view')) {
                return true; // 已存在标签,视为成功
            }

            const finalClassName = className + (isAIByTags ? ' ai-by-tags' : '');

            // 查找合适的图片容器 - 尝试多个可能的父级
            let imgContainer = img.parentElement;

            // 如果直接父级没有合适的定位,向上查找
            while (imgContainer && imgContainer !== node) {
                const style = window.getComputedStyle(imgContainer);
                if (style.position === 'relative' || imgContainer.classList.contains('sc-324476b7-9')) {
                    break;
                }
                imgContainer = imgContainer.parentElement;
            }

            // 如果没找到合适的容器,使用图片的直接父级
            if (!imgContainer || imgContainer === node) {
                imgContainer = img.parentElement;
            }

            if (!imgContainer) {
                return false; // 失败
            }

            // 设置容器为相对定位
            imgContainer.style.position = 'relative';

            // 固定放在左下角,避免与其他元素重叠
            const position = 'bottom: 4px; left: 4px;';

            const tagHtml = `<div class="${finalClassName}" style="position: absolute; ${position} z-index: 10;">${text}</div>`;
            imgContainer.insertAdjacentHTML('afterbegin', tagHtml);

            // 标记节点为已处理
            node.dataset.tagAdded = 'true';

            return true; // 成功
        }
    }

    // ============= 查询数据管理器 =============
    class QueryDataManager {
        constructor(cacheManager, apiClient) {
            this.cacheManager = cacheManager;
            this.apiClient = apiClient;
            this.data = { pixiv_illust: {} };
        }

        getOrCreate(type, id) {
            if (!this.data[type][id]) {
                this.data[type][id] = {
                    nodes: [],
                    querying: false,
                    ai: null,
                    ai_is_possible: null
                };
            }
            return this.data[type][id];
        }

        async addNode(type, id, node) {
            const entry = this.getOrCreate(type, id);

            // 总是添加节点,因为同一个作品可能有多个链接(图片链接和标题链接)
            if (!entry.nodes.includes(node)) {
                entry.nodes.push(node);
            }

            // 预检查缓存
            const cachedData = await this.cacheManager.getCache(type, id);
            if (cachedData) {
                // 静默使用缓存数据
                // 只对当前节点应用缓存数据
                this.applyCachedData(type, id, [node], cachedData);
                // 移除已成功添加标签的节点
                entry.nodes = entry.nodes.filter(n => !n.dataset.tagAdded);
            } else if (!entry.querying) {
                // 静默排队API请求
            }
        }

        applyCachedData(type, id, nodes, cachedData) {
            if (type === 'pixiv_illust') {
                // 延迟处理,给DOM一些时间完全渲染
                setTimeout(() => {
                    nodes.forEach(node => {
                        if (cachedData.ai) {
                            const success = DOMUtils.addTag(node, 'AI', 'add_ai_tag_view', cachedData.isAIByTags);
                            if (success) {
                                this.handleImageRemoval(node);
                            } else {
                                // 如果失败,再次尝试
                                setTimeout(() => {
                                    DOMUtils.addTag(node, 'AI', 'add_ai_tag_view', cachedData.isAIByTags);
                                }, 1000);
                            }
                        } else if (cachedData.ai_is_possible && CONFIG.USER_CONFIG.show_ai_possible) {
                            const success = DOMUtils.addTag(node, 'AI?', 'add_ai_possible_tag_view', cachedData.isAIByTags);
                            if (!success) {
                                setTimeout(() => {
                                    DOMUtils.addTag(node, 'AI?', 'add_ai_possible_tag_view', cachedData.isAIByTags);
                                }, 1000);
                            }
                        }
                    });
                }, 100); // 100ms延迟
            }
        }

        handleImageRemoval(node) {
            try {
                switch (CONFIG.USER_CONFIG.remove_image) {
                    case 1:
                        // 替换所有图像内容为文本
                        const images = node.querySelectorAll('img, canvas, svg');
                        images.forEach(img => {
                            img.outerHTML = "<h5>AI Artwork</h5>";
                        });

                        // 处理背景图像
                        const bgElements = node.querySelectorAll('[style*="background-image"]');
                        bgElements.forEach(el => {
                            el.style.backgroundImage = 'none';
                            if (!el.textContent.trim()) {
                                el.innerHTML = "<h5>AI Artwork</h5>";
                            }
                        });
                        break;

                    case 2:
                        // 移除整个容器
                        const parent = Utils.getParentNodeWithDepth(node, SELECTORS.REMOVE_PARENT_DEPTH);
                        if (parent && parent.parentNode) {
                            parent.parentNode.removeChild(parent);
                        }
                        break;
                }
            } catch (error) {
                Utils.log(`Error handling image removal: ${error.message}`, 'error');
            }
        }

        getQueuedItems() {
            const queued = [];
            for (const [type, items] of Object.entries(this.data)) {
                for (const [id, data] of Object.entries(items)) {
                    if (data.nodes.length > 0 && !data.querying) {
                        queued.push({ type, id, data });
                    }
                }
            }
            return queued;
        }

        async processPixivIllust(id, nodes) {
            const entry = this.getOrCreate('pixiv_illust', id);

            try {
                const cachedData = await this.cacheManager.getCache('pixiv_illust', id);

                if (cachedData) {
                    Utils.log(`Using cached data for illust ${id}`, 'debug');
                    this.applyCachedData('pixiv_illust', id, nodes, cachedData);
                    return false;
                }

                if (entry.ai === null) {
                    entry.querying = true;
                    Utils.log(`Fetching data for illust ${id}`, 'debug');

                    const response = await this.apiClient.fetchPixivIllust(id);
                    const json = await response.json();

                    if (!json?.body) {
                        throw new Error('Invalid response');
                    }

                    const { aiType } = json.body;
                    const tags = json.body.tags?.tags || [];

                    const isAIByType = aiType === 2;
                    const isAIPossibleByType = aiType >= 2;
                    const isAIByTags = CONFIG.USER_CONFIG.enable_tag_detection ? Utils.checkAITags(tags) : false;

                    const cacheData = {
                        ai: isAIByType || isAIByTags,
                        ai_is_possible: isAIPossibleByType || isAIByTags,
                        user_id: json.body.userId,
                        title: json.body.title || '',
                        tags: tags,
                        aiType: aiType,
                        isAIByTags: isAIByTags
                    };

                    entry.ai = cacheData.ai;
                    entry.ai_is_possible = cacheData.ai_is_possible;

                    await this.cacheManager.setCache('pixiv_illust', id, cacheData);
                    this.applyCachedData('pixiv_illust', id, nodes, cacheData);

                    Utils.log(`Processed illust ${id}: AI=${cacheData.ai}`, 'debug');
                    entry.querying = false;
                    return true;
                } else {
                    this.applyCachedData('pixiv_illust', id, nodes, {
                        ai: entry.ai,
                        ai_is_possible: entry.ai_is_possible
                    });
                    return false;
                }
            } catch (error) {
                entry.querying = false;
                Utils.log(`Error processing illust ${id}: ${error.message}`, 'error');
                return false;
            }
        }
    }

    // ============= URL 处理器 =============
    class URLProcessor {
        constructor(queryDataManager) {
            this.queryDataManager = queryDataManager;
        }

        extractPixivIllustId(url) {
            const match = url.match(/\/artworks\/(\d+)/);
            return match ? match[1] : null;
        }

        async processNode(node) {
            if (!node?.href) return;

            if (node.classList.contains('add_ai_tag')) return;

            node.classList.add('add_ai_tag');
            const url = node.href;

            if (/pixiv\.net/.test(url) && /artworks/.test(url)) {
                const id = this.extractPixivIllustId(url);
                if (id) {
                    await this.queryDataManager.addNode('pixiv_illust', id, node);
                }
            }
        }
    }

    // ============= 主应用类 =============
    class PixivAITagEnhanced {
        constructor() {
            this.cacheManager = new CrossTabCacheManager();
            this.syncManager = new CrossTabSyncManager();
            this.apiClient = new APIClient(this.syncManager);
            this.queryDataManager = new QueryDataManager(this.cacheManager, this.apiClient);
            this.urlProcessor = new URLProcessor(this.queryDataManager);
            this.pixivAutoRecorder = new PixivAutoRecorder(this.cacheManager);

            this.isRunning = false;
            this.observer = null;
            this.queryInterval = null;
        }

        async initialize() {
            try {
                Utils.log('Initializing Pixiv AI Tag Enhanced...', 'debug');

                DOMUtils.addStyles();
                await this.pixivAutoRecorder.initialize();
                this.setupObserver();
                this.startQueryLoop();
                this.startMaintenanceLoop();
                // 移除定期扫描,MutationObserver已经足够

                Utils.log('Initialization completed', 'debug');

                // 等待页面完全加载后扫描
                this.waitForPageLoad();

            } catch (error) {
                Utils.log(`Initialization error: ${error.message}`, 'error');
                console.error('Full initialization error:', error);
            }
        }

        async waitForPageLoad() {
            // 如果页面已经完全加载
            if (document.readyState === 'complete') {
                Utils.log('Page already loaded, scanning immediately', 'debug');
                await this.scanDocument();
                return;
            }

            // 等待页面加载完成
            const loadPromise = new Promise((resolve) => {
                if (document.readyState === 'complete') {
                    resolve();
                } else {
                    window.addEventListener('load', resolve, { once: true });
                }
            });

            // 等待DOM内容加载完成(备用)
            const domPromise = new Promise((resolve) => {
                if (document.readyState !== 'loading') {
                    resolve();
                } else {
                    document.addEventListener('DOMContentLoaded', resolve, { once: true });
                }
            });

            try {
                // 等待页面加载完成,最多等待10秒
                await Promise.race([
                    loadPromise,
                    new Promise(resolve => setTimeout(resolve, 10000))
                ]);

                Utils.log('Page load completed, starting scan', 'debug');
                await this.scanDocument();

                // 如果还没有找到链接,再等待一下(可能是SPA应用)
                const artworkCount = document.querySelectorAll('a[href*="/artworks/"]:not(.add_ai_tag)').length;
                if (artworkCount === 0) {
                    Utils.log('No artwork links found, waiting for SPA content...', 'debug');
                    await Utils.sleep(2000);
                    await this.scanDocument();
                }

            } catch (error) {
                Utils.log(`Error waiting for page load: ${error.message}`, 'error');
                // 即使出错也尝试扫描
                await this.scanDocument();
            }
        }

        async scanDocument() {
            try {
                Utils.log('Starting document scan...', 'debug');

                const artworkLinks = document.querySelectorAll('a[href*="/artworks/"]:not(.add_ai_tag)');
                if (artworkLinks.length > 0) {
                    Utils.log(`Found ${artworkLinks.length} new artwork links`, 'debug');
                }

                if (artworkLinks.length > 0) {
                    // 优先处理包含图片的链接,避免重复处理同一作品
                    const processedIds = new Set();
                    let processed = 0;

                    for (const link of artworkLinks) {
                        const id = this.urlProcessor.extractPixivIllustId(link.href);
                        if (id && !processedIds.has(id)) {
                            // 优先选择包含图片的链接
                            if (this.hasImageContent(link)) {
                                await this.urlProcessor.processNode(link);
                                processedIds.add(id);
                                processed++;
                            }
                        }
                    }

                    // 处理没有图片但还未处理的链接
                    for (const link of artworkLinks) {
                        const id = this.urlProcessor.extractPixivIllustId(link.href);
                        if (id && !processedIds.has(id)) {
                            await this.urlProcessor.processNode(link);
                            processedIds.add(id);
                            processed++;
                        }
                    }

                    Utils.log(`Processed ${processed} unique artwork links`, 'debug');
                } else {
                    Utils.log('No new artwork links found', 'debug');
                }
            } catch (error) {
                Utils.log(`Document scan error: ${error.message}`, 'error');
                console.error('Scan error details:', error);
            }
        }

        // 高效的增量扫描 - 只处理新节点
        async scanNewNodes(nodes) {
            try {
                const processedIds = new Set();
                let processed = 0;

                // 收集所有artwork链接
                const allLinks = [];
                for (const node of nodes) {
                    if (node.nodeType === 1) { // Element node
                        // 检查节点本身是否是artwork链接
                        if (node.matches && node.matches('a[href*="/artworks/"]:not(.add_ai_tag)')) {
                            allLinks.push(node);
                        }

                        // 检查节点内部的artwork链接
                        const artworkLinks = node.querySelectorAll('a[href*="/artworks/"]:not(.add_ai_tag)');
                        allLinks.push(...artworkLinks);
                    }
                }

                // 优先处理包含图片的链接
                for (const link of allLinks) {
                    const id = this.urlProcessor.extractPixivIllustId(link.href);
                    if (id && !processedIds.has(id) && this.hasImageContent(link)) {
                        await this.urlProcessor.processNode(link);
                        processedIds.add(id);
                        processed++;
                    }
                }

                // 处理剩余的链接
                for (const link of allLinks) {
                    const id = this.urlProcessor.extractPixivIllustId(link.href);
                    if (id && !processedIds.has(id)) {
                        await this.urlProcessor.processNode(link);
                        processedIds.add(id);
                        processed++;
                    }
                }

                if (processed > 0) {
                    Utils.log(`Incrementally processed ${processed} unique artwork links`, 'debug');
                }

                return processed;
            } catch (error) {
                Utils.log(`Incremental scan error: ${error.message}`, 'error');
                return 0;
            }
        }

        // 检查链接是否包含图像内容
        hasImageContent(link) {
            if (!link) return false;

            // 检查是否包含img标签
            if (link.querySelector('img')) return true;

            // 检查是否包含canvas
            if (link.querySelector('canvas')) return true;

            // 检查是否包含svg
            if (link.querySelector('svg')) return true;

            // 检查是否有背景图像
            const elementsWithBg = link.querySelectorAll('[style*="background-image"]');
            if (elementsWithBg.length > 0) return true;

            // 检查CSS背景图像
            const computedStyle = window.getComputedStyle(link);
            if (computedStyle.backgroundImage && computedStyle.backgroundImage !== 'none') return true;

            // 检查子元素的背景图像
            const children = link.querySelectorAll('*');
            for (const child of children) {
                const childStyle = window.getComputedStyle(child);
                if (childStyle.backgroundImage && childStyle.backgroundImage !== 'none') {
                    return true;
                }
            }

            return false;
        }

        setupObserver() {
            if (this.observer) {
                this.observer.disconnect();
            }

            // 测试MutationObserver是否工作
            Utils.log('Setting up MutationObserver...', 'debug');

            this.observer = new MutationObserver(async (mutations) => {
                Utils.log(`🔍 MutationObserver triggered! ${mutations.length} mutations detected`, 'debug');

                const newNodes = [];
                let totalAddedNodes = 0;

                for (const mutation of mutations) {
                    Utils.log(`  Mutation type: ${mutation.type}, added: ${mutation.addedNodes.length}, removed: ${mutation.removedNodes.length}`, 'debug');

                    if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                        totalAddedNodes += mutation.addedNodes.length;

                        for (const node of mutation.addedNodes) {
                            if (node.nodeType === 1) { // Element node
                                // 检查是否包含artwork链接
                                const hasArtworkLink = (node.matches && node.matches('a[href*="/artworks/"]')) ||
                                    (node.querySelector && node.querySelector('a[href*="/artworks/"]'));

                                if (hasArtworkLink) {
                                    newNodes.push(node);
                                    Utils.log(`  📎 Found node with artwork links: ${node.tagName}`, 'debug');
                                }
                            }
                        }
                    }
                }

                Utils.log(`  Total added nodes: ${totalAddedNodes}, artwork nodes: ${newNodes.length}`, 'debug');

                if (newNodes.length > 0) {
                    Utils.log(`🎯 Processing ${newNodes.length} new nodes with artwork links`, 'debug');
                    await this.scanNewNodes(newNodes);
                }
            });

            // 检查document.body是否存在
            if (!document.body) {
                Utils.log('⚠️ document.body not found, waiting...', 'warn');
                setTimeout(() => this.setupObserver(), 100);
                return;
            }

            try {
                this.observer.observe(document.body, {
                    childList: true,
                    subtree: true
                });

                Utils.log('✅ MutationObserver setup completed, observing document.body', 'debug');

                // 测试observer是否真的在工作
                setTimeout(() => {
                    Utils.log('🧪 Testing MutationObserver by adding a test element...', 'debug');
                    const testDiv = document.createElement('div');
                    testDiv.id = 'pixiv-ai-test';
                    testDiv.style.display = 'none';
                    document.body.appendChild(testDiv);

                    setTimeout(() => {
                        if (document.getElementById('pixiv-ai-test')) {
                            document.body.removeChild(testDiv);
                        }
                    }, 1000);
                }, 2000);

            } catch (error) {
                Utils.log(`❌ Failed to setup MutationObserver: ${error.message}`, 'error');
            }
        }

        startQueryLoop() {
            if (this.isRunning) return;

            this.isRunning = true;

            const interval = CONFIG.USER_CONFIG.query_delay > 0 ?
                CONFIG.USER_CONFIG.query_delay : CONFIG.QUERY_INTERVAL;

            this.queryInterval = setInterval(async () => {
                if (this.isRunning) {
                    await this.processQueuedQueries();
                }
            }, interval);
        }

        async processQueuedQueries() {
            const queuedItems = this.queryDataManager.getQueuedItems();
            if (queuedItems.length === 0) return;

            for (const { type, id, data } of queuedItems) {
                const nodes = [...data.nodes];
                data.nodes = [];

                try {
                    if (type === 'pixiv_illust') {
                        await this.queryDataManager.processPixivIllust(id, nodes);
                    }
                } catch (error) {
                    if (error.message.includes('429')) {
                        data.nodes.unshift(...nodes);
                        Utils.log(`Rate limited for ${type}:${id}, re-queuing`, 'warn');
                    }
                    Utils.log(`Error processing ${type}:${id}: ${error.message}`, 'error');
                }

                await Utils.sleep(100);
            }
        }

        startMaintenanceLoop() {
            setInterval(async () => {
                try {
                    await this.cacheManager.cleanupExpiredCache();
                } catch (error) {
                    Utils.log(`Maintenance error: ${error.message}`, 'error');
                }
            }, CONFIG.CACHE.CLEANUP_INTERVAL);
        }

        startPeriodicScan() {
            // 备用的定期扫描,以防MutationObserver不工作
            setInterval(async () => {
                try {
                    const newLinks = document.querySelectorAll('a[href*="/artworks/"]:not(.add_ai_tag)').length;
                    if (newLinks > 0) {
                        Utils.log(`🔄 Periodic scan found ${newLinks} new artwork links`, 'info');
                        await this.scanDocument();
                    }
                    // 移除了"没有找到新链接"的日志,减少噪音
                } catch (error) {
                    Utils.log(`Periodic scan error: ${error.message}`, 'error');
                }
            }, 5000); // 每5秒检查一次
        }

        async stop() {
            this.isRunning = false;

            if (this.queryInterval) {
                clearInterval(this.queryInterval);
                this.queryInterval = null;
            }

            if (this.observer) {
                this.observer.disconnect();
                this.observer = null;
            }

            await this.syncManager.stop();
            Utils.log('Pixiv AI Tag Enhanced stopped', 'info');
        }
    }

    // ============= 初始化 =============
    let enhancer = null;


    async function initialize() {
        try {
            if (enhancer) {
                await enhancer.stop();
            }

            enhancer = new PixivAITagEnhanced();
            await enhancer.initialize();

        } catch (error) {
            Utils.log(`Initialization failed: ${error.message}`, 'error');
            console.error('Full error:', error);
        }
    }

    // 确保在DOM准备好后初始化
    if (document.readyState === 'loading') {
        Utils.log('Document still loading, waiting for DOMContentLoaded', 'debug');
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        Utils.log('Document ready, initializing immediately', 'debug');
        initialize();
    }

})();