Greasy Fork is available in English.
DeepFlood & NodeSeek 论坛增强脚本(基于 NSaide 改造)
当前为
// ==UserScript==
// @name NSDF 助手
// @namespace https://www.deepflood.com/
// @version 0.2.2
// @description DeepFlood & NodeSeek 论坛增强脚本(基于 NSaide 改造)
// @author zen1zi
// @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("⚙️ 清理缓存", () => {
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();
}
})();