Greasy Fork

Greasy Fork is available in English.

NSDF 助手

DeepFlood & NodeSeek 论坛增强脚本(基于 NSaide 改造)

当前为 2025-10-12 提交的版本,查看 最新版本

// ==UserScript==
// @name         NSDF 助手
// @namespace    https://www.deepflood.com/
// @version      0.2.0
// @description  DeepFlood & NodeSeek 论坛增强脚本(基于 NSaide 改造)
// @author       
// @license      GPL-3.0
// @match        https://www.deepflood.com/*
// @match        https://deepflood.com/*
// @match        https://www.nodeseek.com/*
// @match        https://nodeseek.com/*
// @icon         https://www.deepflood.com/favicon.ico
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_listValues
// @grant        GM_info
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==
(function() {
    'use strict';

    console.log('[DF助手] 脚本开始加载');

    const HOST_SITE_MAP = {
        'www.deepflood.com': { id: 'deepflood', name: 'DeepFlood' },
        'deepflood.com': { id: 'deepflood', name: 'DeepFlood' },
        'www.nodeseek.com': { id: 'nodeseek', name: 'NodeSeek' },
        'nodeseek.com': { id: 'nodeseek', name: 'NodeSeek' }
    };

    const currentHost = window.location.host;
    const siteMeta = HOST_SITE_MAP[currentHost] || { id: 'unknown', name: currentHost };
    const siteInfo = Object.freeze({
        ...siteMeta,
        host: currentHost,
        origin: window.location.origin,
        isDeepFlood: siteMeta.id === 'deepflood',
        isNodeSeek: siteMeta.id === 'nodeseek'
    });

    if (siteInfo.id === 'unknown') {
        console.warn('[DF助手] 检测到未配置站点,尝试按当前域名加载');
    } else {
        console.log(`[DF助手] 当前站点: ${siteInfo.name} (${siteInfo.host})`);
    }

    const CONFIG_URL = 'https://raw.githubusercontent.com/zen1zi/NSDF_Plus/main/modules/config.json';
    const CACHE_EXPIRY = 30 * 60 * 1000;
    const CACHE_KEY_PREFIX = 'df_module_cache_';
    const CONFIG_CACHE_KEY = 'df_config_cache';
    const MODULE_ENABLED_KEY_PREFIX = 'df_MODULE_ENABLED_';
    const LEGACY_MODULE_ENABLED_KEY_PREFIX = 'module_';
    const LEGACY_MODULE_ENABLED_KEY_SUFFIX = '_enabled';

    const normalizeModuleIdForKey = (id) => {
        if (typeof id !== 'string') {
            return '';
        }

        return id
            .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
            .replace(/[^a-z0-9]/gi, '_')
            .toUpperCase();
    };

    const getCachedData = (key) => {
        const cached = GM_getValue(key);
        if (typeof cached !== 'string' || cached.length === 0) {
            console.log(`[DF助手] 缓存未命中: ${key}`);
            return null;
        }

        try {
            const { data, timestamp } = JSON.parse(cached);
            const age = Date.now() - timestamp;
            if (age > CACHE_EXPIRY) {
                console.log(`[DF助手] 缓存已过期: ${key} (${Math.round(age / 1000)}s 前)`);
                deleteCachedData(key, { silent: true });
                return null;
            }
            console.log(`[DF助手] 缓存命中: ${key} (${Math.round(age / 1000)}s 前)`);
            return data;
        } catch (error) {
            console.warn(`[DF助手] 缓存数据解析失败: ${key}`, error);
            deleteCachedData(key, { silent: true });
            return null;
        }
    };

    const setCachedData = (key, data) => {
        try {
            GM_setValue(key, JSON.stringify({
                data,
                timestamp: Date.now()
            }));
            console.log(`[DF助手] 缓存已保存: ${key}`);
        } catch (error) {
            console.error(`[DF助手] 缓存保存失败: ${key}`, error);
        }
    };

    const deleteCachedData = (key, { silent = false } = {}) => {
        try {
            GM_deleteValue(key);
            if (!silent) {
                console.log(`[DF助手] 缓存已清理: ${key}`);
            }
        } catch (error) {
            console.error(`[DF助手] 缓存清理失败: ${key}`, error);
        }
    };

    const fetchWithCache = (url, cacheKey, retryCount = 3) => {
        return new Promise((resolve, reject) => {
            const cached = getCachedData(cacheKey);
            if (cached) {
                resolve(cached);
                return;
            }

            console.log(`[DF助手] 开始远程获取: ${url}`);

            const attemptFetch = (attempt) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: `${url}?t=${Date.now()}`,
                    timeout: 10000,
                    nocache: true,
                    headers: {
                        'Cache-Control': 'no-cache',
                        'Pragma': 'no-cache'
                    },
                    onload: (response) => {
                        if (response.status === 200) {
                            try {
                                const data = response.responseText;
                                setCachedData(cacheKey, data);
                                console.log(`[DF助手] 远程获取成功: ${url}`);
                                resolve(data);
                            } catch (error) {
                                console.error(`[DF助手] 数据处理失败: ${url}`, error);
                                reject(error);
                            }
                        } else {
                            const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
                            if (attempt < retryCount) {
                                console.warn(`[DF助手] 请求失败,重试 ${attempt}/${retryCount}: ${url}`, error.message);
                                setTimeout(() => attemptFetch(attempt + 1), 1000 * attempt);
                            } else {
                                console.error(`[DF助手] 请求最终失败: ${url}`, error);
                                reject(error);
                            }
                        }
                    },
                    onerror: (error) => {
                        if (attempt < retryCount) {
                            console.warn(`[DF助手] 网络错误,重试 ${attempt}/${retryCount}: ${url}`, error);
                            setTimeout(() => attemptFetch(attempt + 1), 1000 * attempt);
                        } else {
                            console.error(`[DF助手] 网络错误最终失败: ${url}`, error);
                            reject(error);
                        }
                    },
                    ontimeout: () => {
                        const error = new Error('请求超时');
                        if (attempt < retryCount) {
                            console.warn(`[DF助手] 请求超时,重试 ${attempt}/${retryCount}: ${url}`);
                            setTimeout(() => attemptFetch(attempt + 1), 1000 * attempt);
                        } else {
                            console.error(`[DF助手] 请求超时最终失败: ${url}`);
                            reject(error);
                        }
                    }
                });
            };

            attemptFetch(1);
        });
    };

    const loadConfig = async () => {
        try {
            const configText = await fetchWithCache(CONFIG_URL, CONFIG_CACHE_KEY);
            return JSON.parse(configText);
        } catch (error) {
            console.error('[DF助手] 配置加载失败:', error);
            throw error;
        }
    };

    const loadModule = async (moduleInfo) => {
        const cacheKey = `${CACHE_KEY_PREFIX}${moduleInfo.id}`;
        const loadStartTime = Date.now();

        try {
            console.log(`[DF助手] 开始加载模块: ${moduleInfo.name}`);
            const moduleCode = await fetchWithCache(moduleInfo.url, cacheKey);

            // 检查模块代码安全性 - 简单的恶意代码检测
            if (moduleCode.includes('eval(') && !moduleCode.includes('// Safe eval')) {
                console.warn(`[DF助手] 模块 ${moduleInfo.name} 包含可疑代码,跳过加载`);
                return;
            }

            eval(moduleCode);
            const loadTime = Date.now() - loadStartTime;
            console.log(`[DF助手] 模块加载成功: ${moduleInfo.name} (${loadTime}ms)`);

            // 记录模块加载性能
            window.DF.dev.moduleLoadTimes = window.DF.dev.moduleLoadTimes || {};
            window.DF.dev.moduleLoadTimes[moduleInfo.id] = loadTime;

        } catch (error) {
            console.error(`[DF助手] 模块 ${moduleInfo.name} 加载失败:`, error);

            // 尝试从缓存中清除损坏的模块
            deleteCachedData(cacheKey, { silent: true });
            console.log(`[DF助手] 已清理损坏的模块缓存: ${moduleInfo.id}`);

            throw error;
        }
    };

    const createDF = () => {
        window.DF = {
            version: GM_info.script.version,
            modules: new Map(),
            isReady: false,
            site: siteInfo,

            registerModule(moduleDefinition) {
                if (!moduleDefinition || !moduleDefinition.id || !moduleDefinition.init) return;

                const normalizedId = normalizeModuleIdForKey(moduleDefinition.id);
                const enabledKey = `${MODULE_ENABLED_KEY_PREFIX}${normalizedId}`;
                let enabled = GM_getValue(enabledKey);

                if (typeof enabled === 'undefined') {
                    const legacyKey = `${LEGACY_MODULE_ENABLED_KEY_PREFIX}${moduleDefinition.id}${LEGACY_MODULE_ENABLED_KEY_SUFFIX}`;
                    const legacyValue = GM_getValue(legacyKey);

                    if (typeof legacyValue !== 'undefined') {
                        enabled = legacyValue;
                        try {
                            GM_setValue(enabledKey, legacyValue);
                            GM_deleteValue(legacyKey);
                            console.log(`[DF助手] 已迁移模块启用状态: ${moduleDefinition.id}`);
                        } catch (migrationError) {
                            console.warn(`[DF助手] 模块启用状态迁移失败: ${moduleDefinition.id}`, migrationError);
                        }
                    } else {
                        enabled = true;
                        try {
                            GM_setValue(enabledKey, enabled);
                        } catch (error) {
                            console.warn(`[DF助手] 默认写入模块启用状态失败: ${moduleDefinition.id}`, error);
                        }
                    }
                }

                if (typeof enabled === 'string') {
                    enabled = enabled.toLowerCase() === 'true';
                } else {
                    enabled = Boolean(enabled);
                }

                const module = {
                    ...moduleDefinition,
                    enabled: enabled,
                    enabledStorageKey: enabledKey
                };

                this.modules.set(moduleDefinition.id, module);
                console.log(`[DF助手] 模块已注册: ${module.name}`);
            },

            init() {
                if (this.isReady) return;

                const enabledModules = Array.from(this.modules.values()).filter(m => m.enabled);
                console.log(`[DF助手] 开始初始化 ${enabledModules.length} 个已启用模块`);

                Promise.all(enabledModules.map(module =>
                    new Promise(resolve => {
                        try {
                            module.init();
                            console.log(`[DF助手] 模块初始化成功: ${module.name}`);
                            resolve();
                        } catch (error) {
                            console.error(`[DF助手] 模块 ${module.name} 初始化失败:`, error);
                            resolve();
                        }
                    })
                )).then(() => {
                    this.isReady = true;
                    console.log('[DF助手] 所有模块初始化完成');
                });
            }
        };

        window.DF.getSiteUrl = (path = '') => {
            if (typeof path !== 'string' || path.length === 0) {
                return window.DF.site.origin;
            }

            if (/^https?:\/\//i.test(path)) {
                return path;
            }

            const normalizedPath = path.startsWith('/') ? path : `/${path}`;
            return `${window.DF.site.origin}${normalizedPath}`;
        };

        window.DFRegisterModule = (moduleDefinition) => {
            window.DF.registerModule(moduleDefinition);
        };

        // 缓存工具 - 可供模块使用
        window.DF.cache = {
            get: getCachedData,
            set: setCachedData,
            delete: deleteCachedData,
            fetch: fetchWithCache,
            expiry: CACHE_EXPIRY,
            keyPrefix: CACHE_KEY_PREFIX
        };

        // 开发者工具
        window.DF.dev = {
            clearCache() {
                const removedKeys = [];

                try {
                    const storedKeys = GM_listValues();
                    storedKeys.forEach((key) => {
                        if (key === CONFIG_CACHE_KEY || key.startsWith(CACHE_KEY_PREFIX)) {
                            deleteCachedData(key, { silent: true });
                            removedKeys.push(key);
                        }
                    });
                } catch (error) {
                    console.error('[DF助手] 遍历缓存键失败:', error);
                }

                console.log(`[DF助手] 已清理缓存: ${removedKeys.length} 项`);
                return removedKeys;
            },
            getCacheInfo() {
                const info = {};
                let storedKeys = [];

                try {
                    storedKeys = GM_listValues();
                } catch (error) {
                    console.error('[DF助手] 获取缓存信息失败:', error);
                }

                const storedSet = new Set(storedKeys);
                info.config = storedSet.has(CONFIG_CACHE_KEY) ? '已缓存' : '未缓存';
                window.DF.modules.forEach((module, id) => {
                    const moduleCacheKey = `${CACHE_KEY_PREFIX}${id}`;
                    info[id] = storedSet.has(moduleCacheKey) ? '已缓存' : '未缓存';
                });
                return info;
            },
            getModuleHealth() {
                const health = {};
                window.DF.modules.forEach((module, id) => {
                    health[id] = {
                        id: module.id,
                        name: module.name,
                        enabled: module.enabled,
                        loaded: !!module.init,
                        cached: typeof GM_getValue(`${CACHE_KEY_PREFIX}${id}`) === 'string'
                    };
                });
                return health;
            }
        };
    };

    const initializeModules = async () => {
        const initStartTime = Date.now();

        try {
            console.log('[DF助手] 开始初始化模块系统');
            createDF();

            const config = await loadConfig();
            console.log(`[DF助手] 配置加载成功,发现 ${config.modules.length} 个模块`);

            // 并行加载模块,但容错处理
            const moduleLoadPromises = config.modules.map(async (moduleInfo) => {
                try {
                    await loadModule(moduleInfo);
                    return { success: true, module: moduleInfo.id };
                } catch (error) {
                    console.error(`[DF助手] 模块 ${moduleInfo.id} 加载失败:`, error);
                    return { success: false, module: moduleInfo.id, error };
                }
            });

            const results = await Promise.all(moduleLoadPromises);
            const successful = results.filter(r => r.success).length;
            const failed = results.filter(r => !r.success);

            console.log(`[DF助手] 模块加载完成: ${successful}/${config.modules.length} 成功`);

            if (failed.length > 0) {
                console.warn('[DF助手] 加载失败的模块:', failed.map(f => f.module));
            }

            if (window.DF.modules.size > 0) {
                window.DF.init();
                const initTime = Date.now() - initStartTime;
                console.log(`[DF助手] 初始化完成 (${initTime}ms),已加载 ${window.DF.modules.size} 个模块`);

                // 注册菜单命令
                GM_registerMenuCommand('DF助手 - 打开设置', () => {
                    const settingsModule = window.DF.modules.get('settings');
                    if (settingsModule && settingsModule.utils && settingsModule.utils.showSettingsPanel) {
                        settingsModule.utils.showSettingsPanel();
                    } else {
                        console.warn('[DF助手] 设置模块未加载或不可用');
                    }
                });

                GM_registerMenuCommand('DF助手 - 清理缓存', () => {
                    window.DF.dev.clearCache();
                    alert('缓存已清理,请刷新页面');
                });
            } else {
                console.warn('[DF助手] 没有模块成功加载');
            }

        } catch (error) {
            console.error('[DF助手] 初始化失败:', error);

            // 降级处理:尝试只加载设置模块
            try {
                console.log('[DF助手] 尝试降级启动...');
                const fallbackConfig = {
                    modules: [{
                        id: 'settings',
                        name: '设置面板',
                        url: 'https://raw.githubusercontent.com/zen1zi/NSDF_Plus/main/modules/settings/index.js'
                    }]
                };

                await loadModule(fallbackConfig.modules[0]);
                if (window.DF.modules.size > 0) {
                    window.DF.init();
                    console.log('[DF助手] 降级启动成功');
                }
            } catch (fallbackError) {
                console.error('[DF助手] 降级启动也失败:', fallbackError);
            }
        }
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeModules);
    } else {
        initializeModules();
    }
})();