Greasy Fork is available in English.
DeepFlood & NodeSeek 论坛增强脚本(基于 NSaide 改造)
当前为
// ==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();
}
})();