文章详情

返回首页

CF搭建节点管理v1.0-20251127

分享文章 作者: Ws01 创建时间: 2025-11-27 更新时间: 2025-11-27 📝 字数: 80,660 字 👁️ 阅读: 21 次

// 部署完成后在网址后面加上这个,获取自建节点和机场聚合节点,/?token=xxoo&tag=9527abc-jichang
// 默认节点信息,聚合订阅地址:https://域名/?token=5758bf7a-87ad-4b69-a48c-c9c0bd4cfc1f&tag=9527abc-jichang
// 部署完成后在网址后面加上这个,只获取自建节点,/?token=xxoo
// 登录管理页面:https://域名/9527kkk/login
// CF的kv数据库邦定名称NODES_KV

const mytoken = '5758bf7a-87ad-4b69-a48c-c9c0bd4cfc1f'; //可以随便取,或者uuid生成,https://1024tools.com/uuid
const tgbottoken =''; //可以为空,或者@BotFather中输入/start,/newbot,并关注机器人
const tgchatid =''; //可以为空,或者@userinfobot中获取,/start

// 登录认证配置
const LOGIN_USERNAME = 'admin'; // 默认用户名,设置中修改,添加变量名USERNAME
const LOGIN_PASSWORD = 'admin888'; // 默认密码,设置中修改,添加变量名PASSWORD
const LOGIN_PATH = '/9527kkk/login'; // 登录页面路径
const ADMIN_PATH = '/9527kkk/'; // 管理页面路径

// 从环境变量获取登录凭据(如果设置了的话)
const ENVUSERNAME = typeof USERNAME !== 'undefined' ? USERNAME : LOGINUSERNAME;
const ENVPASSWORD = typeof PASSWORD !== 'undefined' ? PASSWORD : LOGINPASSWORD;

// KV存储键名
const KV_KEYS = {
CUSTOMNODES: 'customnodes',
SUBSCRIPTIONURLS: 'subscriptionurls',
AUTHSESSIONS: 'authsessions'
};

// 内存存储作为fallback
let memoryStorage = {
custom_nodes: [],
subscription_urls: [],
auth_sessions: {}
};

// 创建fallback存储对象
let fallbackStorage = {
async get(key) {
console.log(KV Get (fallback): ${key});
return memoryStorage[key] ? JSON.stringify(memoryStorage[key]) : null;
},
async put(key, value) {
console.log(KV Put (fallback): ${key} = ${value});
try {
memoryStorage[key] = JSON.parse(value);
return true;
} catch (error) {
console.error('Memory storage error:', error);
return false;
}
},
async delete(key) {
console.log(KV Delete (fallback): ${key});
delete memoryStorage[key];
return true;
}
};

// 检查KV绑定状态
console.log('检查KV绑定状态...');

// 检查是否已经有KV绑定(Cloudflare会自动注入绑定的变量)
let usingRealKV = false;

// 方法1: 检查全局变量NODES_KV是否被Cloudflare注入
if (typeof NODESKV !== 'undefined' && NODESKV !== fallbackStorage) {
usingRealKV = true;
console.log('✅ 检测到KV绑定 (方法1) - 数据将持久保存');
}

// 方法2: 检查是否有KV绑定对象
if (!usingRealKV && typeof NODESKVBINDING !== 'undefined') {
NODESKV = NODESKV_BINDING;
usingRealKV = true;
console.log('✅ 检测到KV绑定 (方法2) - 数据将持久保存');
}

// 方法3: 尝试直接访问绑定的变量
if (!usingRealKV) {
try {
// 在Cloudflare Workers中,绑定的变量会直接可用
if (typeof NODESKV !== 'undefined' && NODESKV && typeof NODES_KV.get === 'function') {
usingRealKV = true;
console.log('✅ 检测到KV绑定 (方法3) - 数据将持久保存');
}
} catch (error) {
console.log('KV检测方法3失败:', error);
}
}

if (!usingRealKV) {
NODES_KV = fallbackStorage;
console.log('⚠️ 使用内存存储fallback - 数据在Worker重启后会丢失');
console.log('请确保在Worker设置中正确绑定了KV存储,变量名为: NODES_KV');
console.log('当前NODESKV类型:', typeof NODESKV);
console.log('当前NODESKV值:', NODESKV);
}

addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) })

async function handleRequest(request) {
const url = new URL(request.url);
const pathname = url.pathname;
const token = url.searchParams.get('token');
const tag = url.searchParams.get('tag');

// 处理静态资源请求(不需要token验证)
if (pathname === '/favicon.ico' || pathname.startsWith('/static/') || pathname.endsWith('.css') || pathname.endsWith('.js')) {
return new Response('', { status: 404 });
}

// 登录页面路由
if (pathname === LOGIN_PATH) {
return handleLoginPage(request);
}

// 登录API路由
if (pathname === LOGIN_PATH + '/auth') {
return handleLoginAuth(request);
}

// 登出API路由
if (pathname === LOGIN_PATH + '/logout') {
return handleLogout(request);
}

// 管理页面路由(需要登录验证)
if (pathname === ADMIN_PATH) {
return handleAdminPageWithAuth(request);
}

// 旧的管理页面路由(保持兼容性,但需要token验证)
if (pathname === '/admin') {
if (token !== mytoken) {
return new Response('Invalid token???', { status: 403 });
}
return handleAdminPage(request);
}

// API路由(需要登录验证)
if (pathname.startsWith('/api/')) {
return handleAPIWithAuth(request);
}

// 原有的节点订阅逻辑(保持原有token验证)
if (token !== mytoken) {
return new Response('Invalid token???', { status: 403 });
}

return handleSubscription(request, tag);
}

async function handleAdminPage(request) {
const html = `

节点管理后台

节点管理后台 v1.0

管理自建节点和机场订阅链接

欢迎,管理员
检查存储状态中...

添加自建节点

自建节点列表

序号 节点名称 节点配置 操作
加载中...

添加机场订阅

机场订阅列表

序号 订阅名称 订阅链接 操作
加载中...
`;

return new Response(html, {
headers: { 'Content-Type': 'text/html; charset=utf-8' }
});
}

// 登录页面处理函数
async function handleLoginPage(request) {
const html = `

节点管理后台 - 登录 `;

return new Response(html, {
headers: { 'Content-Type': 'text/html; charset=utf-8' }
});
}

// 登录认证API
async function handleLoginAuth(request) {
try {
const { username, password } = await request.json();

// 验证用户名和密码
if (username === ENVUSERNAME && password === ENVPASSWORD) {
// 生成会话ID
const sessionId = generateSessionId();
const sessionData = {
username: username,
loginTime: Date.now(),
expires: Date.now() + (24 60 60 * 1000) // 24小时过期
};

// 存储会话到KV
await NODESKV.put(session${sessionId}, JSON.stringify(sessionData));

// 设置Cookie
const response = new Response(JSON.stringify({
success: true,
message: '登录成功'
}), {
headers: {
'Content-Type': 'application/json',
'Set-Cookie': session=${sessionId}; Path=/; HttpOnly; Max-Age=86400; SameSite=Strict
}
});

return response;
} else {
return new Response(JSON.stringify({
success: false,
error: '用户名或密码错误'
}), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
} catch (error) {
return new Response(JSON.stringify({
success: false,
error: '登录验证失败'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 登出API
async function handleLogout(request) {
try {
const sessionId = getSessionIdFromRequest(request);

if (sessionId) {
// 删除会话
await NODESKV.delete(session${sessionId});
}

return new Response(JSON.stringify({
success: true,
message: '登出成功'
}), {
headers: {
'Content-Type': 'application/json',
'Set-Cookie': 'session=; Path=/; HttpOnly; Max-Age=0; SameSite=Strict'
}
});
} catch (error) {
return new Response(JSON.stringify({
success: false,
error: '登出失败'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 生成会话ID
function generateSessionId() {
return 'sess' + Date.now() + '' + Math.random().toString(36).substr(2, 9);
}

// 从请求中获取会话ID
function getSessionIdFromRequest(request) {
const cookieHeader = request.headers.get('Cookie');
if (!cookieHeader) return null;

const cookies = cookieHeader.split(';').map(c => c.trim());
const sessionCookie = cookies.find(c => c.startsWith('session='));

if (sessionCookie) {
return sessionCookie.split('=')[1];
}

return null;
}

// 验证会话
async function validateSession(request) {
try {
const sessionId = getSessionIdFromRequest(request);

if (!sessionId) {
return { valid: false, reason: 'No session' };
}

const sessionData = await NODESKV.get(session${sessionId});

if (!sessionData) {
return { valid: false, reason: 'Session not found' };
}

const session = JSON.parse(sessionData);

// 检查会话是否过期
if (Date.now() > session.expires) {
// 删除过期会话
await NODESKV.delete(session${sessionId});
return { valid: false, reason: 'Session expired' };
}

return { valid: true, session: session };
} catch (error) {
return { valid: false, reason: 'Session validation error' };
}
}

// 带认证的管理页面处理函数
async function handleAdminPageWithAuth(request) {
// 验证登录状态
const sessionValidation = await validateSession(request);

if (!sessionValidation.valid) {
// 未登录,重定向到登录页面
return new Response(null, {
status: 302,
headers: {
'Location': LOGIN_PATH
}
});
}

// 已登录,显示管理页面
return handleAdminPage(request);
}

// 带认证的API处理函数
async function handleAPIWithAuth(request) {
// 验证登录状态
const sessionValidation = await validateSession(request);

if (!sessionValidation.valid) {
return new Response(JSON.stringify({
success: false,
error: '未登录或会话已过期,请重新登录'
}), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}

// 已登录,处理API请求
return handleAPI(request);
}

// API处理函数
async function handleAPI(request) {
const url = new URL(request.url);
const pathname = url.pathname;
const method = request.method;

// 自建节点API
if (pathname === '/api/custom-nodes') {
if (method === 'GET') {
return getCustomNodes();
} else if (method === 'POST') {
const data = await request.json();
return addCustomNode(data);
}
}

// 删除自建节点API
if (pathname.startsWith('/api/custom-nodes/') && method === 'DELETE') {
const id = pathname.split('/')[3];
return deleteCustomNode(id);
}

// 更新自建节点API
if (pathname.startsWith('/api/custom-nodes/') && method === 'PUT') {
const id = pathname.split('/')[3];
const data = await request.json();
return updateCustomNode(id, data);
}

// 批量删除自建节点API
if (pathname === '/api/custom-nodes/batch-delete' && method === 'POST') {
const data = await request.json();
return batchDeleteCustomNodes(data);
}

// 机场订阅API
if (pathname === '/api/subscriptions') {
if (method === 'GET') {
return getSubscriptions();
} else if (method === 'POST') {
const data = await request.json();
return addSubscription(data);
}
}

// 删除机场订阅API
if (pathname.startsWith('/api/subscriptions/') && method === 'DELETE') {
const id = pathname.split('/')[3];
return deleteSubscription(id);
}

// 更新机场订阅API
if (pathname.startsWith('/api/subscriptions/') && method === 'PUT') {
const id = pathname.split('/')[3];
const data = await request.json();
return updateSubscription(id, data);
}

// 存储状态检查API
if (pathname === '/api/storage-status') {
return checkStorageStatus();
}

// KV测试API
if (pathname === '/api/kv-test') {
return testKVConnection();
}

// 节点名称解码测试API
if (pathname === '/api/decode-test') {
return testNodeNameDecoding();
}

// Base64解码测试API
if (pathname === '/api/base64-test') {
return testBase64Decoding();
}

return new Response('Not Found', { status: 404 });
}

// 检查存储状态
async function checkStorageStatus() {
// 检查KV是否被正确绑定
let usingKV = false;
let storageType = '内存存储';
let message = '数据在Worker重启后会丢失';

// 检查是否使用了真实的KV存储
if (NODES_KV !== fallbackStorage) {
usingKV = true;
storageType = 'KV存储';
message = '数据将持久保存';
}

return new Response(JSON.stringify({
usingKV: usingKV,
storageType: storageType,
message: message,
debug: {
hasNODESKV: typeof NODESKV !== 'undefined',
isFallbackStorage: NODES_KV === fallbackStorage,
hasNODESKVBINDING: typeof NODESKVBINDING !== 'undefined',
NODESKVtype: typeof NODES_KV
}
}), {
headers: { 'Content-Type': 'application/json' }
});
}

// 测试KV连接
async function testKVConnection() {
const testKey = 'kvtest' + Date.now();
const testValue = 'testvalue' + Math.random();

try {
// 尝试写入测试数据
await NODES_KV.put(testKey, testValue);

// 尝试读取测试数据
const retrievedValue = await NODES_KV.get(testKey);

// 清理测试数据
try {
await NODES_KV.delete(testKey);
} catch (deleteError) {
console.log('清理测试数据失败:', deleteError);
}

const isKVWorking = retrievedValue === testValue;

return new Response(JSON.stringify({
success: true,
kvWorking: isKVWorking,
testKey: testKey,
testValue: testValue,
retrievedValue: retrievedValue,
storageType: isKVWorking ? 'KV存储' : '内存存储',
message: isKVWorking ? 'KV存储工作正常' : 'KV存储未正确配置',
debug: {
NODESKVtype: typeof NODES_KV,
NODESKVconstructor: NODES_KV?.constructor?.name,
hasGet: typeof NODES_KV?.get === 'function',
hasPut: typeof NODES_KV?.put === 'function',
hasDelete: typeof NODES_KV?.delete === 'function'
}
}), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(JSON.stringify({
success: false,
error: error.message,
kvWorking: false,
storageType: '内存存储',
message: 'KV测试失败: ' + error.message,
debug: {
NODESKVtype: typeof NODES_KV,
error_stack: error.stack
}
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 测试节点名称解码
async function testNodeNameDecoding() {
const testConfig = 'vless://7a169e43-ff85-4572-9843-ba7207d07319@192.9.162.122:1443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=swdist.apple.com&fp=qq&pbk=ZIBYUH_qQSeI1T6xImXG6MEZXP2yZW3NqGa8W69Cfyk&sid=dde50f55d81116&spx=%2F&type=tcp&headerType=none#%E6%82%89%E5%B0%BC%E5%A4%A7%E9%99%86%E4%BC%98%E5%8C%96BGP%E7%BA%BF%E8%B7%AF';

let nodeName = '';
if (testConfig.includes('#')) {
const namePart = testConfig.split('#').pop().trim();
try {
nodeName = decodeURIComponent(namePart);
} catch (e) {
nodeName = namePart;
}
}

return new Response(JSON.stringify({
success: true,
originalConfig: testConfig,
encodedName: testConfig.split('#').pop(),
decodedName: nodeName,
testResult: nodeName === '悉尼大陆优化BGP线路'
}), {
headers: { 'Content-Type': 'application/json' }
});
}

// 测试Base64解码
async function testBase64Decoding() {
const testBase64 = 'aHlzdGVyaWEyOi8vNzljNGZlMTEtOTc4Ny00MDZiLWJmOTQtYzFjMWRiZjU5ZTI4QDc3LjIyMy4yMTQuMTkzOjMxNDY4P3NuaT13d3cuYmluZy5jb20maW5zZWN1cmU9MSNpbG92ZXlvdSUyMC0lMjAlRjAlOUYlOTIlOEUlREElQTklRDglQTclRDklODYlRDklODElREIlOEMlREElQUYlMjAlRDklODclRDglQTclREIlOEMlMjAlRDglQTglREIlOEMlRDglQjQlRDglQUElRDglQjElMjAlRDglQUYlRDglQjElMjAlREElODYlRDklODYlRDklODQlMjAlRDglQUElRDklODQlREElQUYlRDglQjElRDglQTcuLi4NCg==';

try {
const decodedConfig = atob(testBase64);
console.log('Base64解码测试:', decodedConfig);

// 解析解码后的配置
const lines = decodedConfig.split('\n').map(line => line.trim()).filter(line => line);
const nodes = [];

for (const line of lines) {
if (line) {
const node = processNodeConfig(line, [], nodes);
if (node) {
nodes.push(node);
}
}
}

return new Response(JSON.stringify({
success: true,
originalBase64: testBase64,
decodedConfig: decodedConfig,
parsedNodes: nodes,
nodeCount: nodes.length,
isBase64Detected: isBase64Encoded(testBase64)
}), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(JSON.stringify({
success: false,
error: error.message,
originalBase64: testBase64,
isBase64Detected: isBase64Encoded(testBase64)
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 获取自建节点
async function getCustomNodes() {
try {
const data = await NODESKV.get(KVKEYS.CUSTOM_NODES);
const nodes = data ? JSON.parse(data) : [];
return new Response(JSON.stringify(nodes), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(JSON.stringify([]), {
headers: { 'Content-Type': 'application/json' }
});
}
}

// 添加自建节点
async function addCustomNode(data) {
try {
console.log('Adding custom nodes:', data);

// 验证输入数据
if (!data.config) {
return new Response(JSON.stringify({
success: false,
error: '节点配置不能为空'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}

const existingData = await NODESKV.get(KVKEYS.CUSTOM_NODES);
console.log('Existing data:', existingData);

const nodes = existingData ? JSON.parse(existingData) : [];
console.log('Current nodes count:', nodes.length);

// 解析多个节点配置
const configLines = data.config.split('\n').map(line => line.trim()).filter(line => line);
const newNodes = [];
const duplicateNodes = [];

for (let i = 0; i < configLines.length; i++) {
let config = configLines[i];

// 检测并解码Base64编码的节点配置
if (isBase64Encoded(config)) {
try {
const decodedConfig = atob(config);
console.log('Base64解码前:', config);
console.log('Base64解码后:', decodedConfig);

// 如果解码后包含多个节点(用换行分隔),分别处理
const decodedLines = decodedConfig.split('\n').map(line => line.trim()).filter(line => line);
for (const decodedLine of decodedLines) {
if (decodedLine) {
const node = processNodeConfig(decodedLine, nodes, newNodes);
if (node) {
newNodes.push(node);
} else {
// 记录重复的节点
duplicateNodes.push(decodedLine);
}
}
}
continue; // 跳过下面的单个节点处理
} catch (error) {
console.error('Base64解码失败:', error);
// 如果解码失败,继续按普通配置处理
}
}

// 处理普通节点配置
const node = processNodeConfig(config, nodes, newNodes);
if (node) {
newNodes.push(node);
} else {
// 记录重复的节点
duplicateNodes.push(config);
}
}

// 添加新节点到现有列表
nodes.push(...newNodes);
console.log('New nodes count:', nodes.length);
console.log('Added nodes:', newNodes.length);
console.log('Duplicate nodes:', duplicateNodes.length);

const putResult = await NODESKV.put(KVKEYS.CUSTOM_NODES, JSON.stringify(nodes));
console.log('Put result:', putResult);

// 构建响应消息
let message = '';
if (newNodes.length > 0 && duplicateNodes.length > 0) {
message = 成功添加 ${newNodes.length} 个节点,跳过 ${duplicateNodes.length} 个重复节点;
} else if (newNodes.length > 0) {
message = 成功添加 ${newNodes.length} 个节点;
} else if (duplicateNodes.length > 0) {
message = 所有 ${duplicateNodes.length} 个节点都已存在,未添加任何新节点;
} else {
message = '没有有效的节点配置';
}

return new Response(JSON.stringify({
success: true,
addedCount: newNodes.length,
duplicateCount: duplicateNodes.length,
message: message,
duplicates: duplicateNodes
}), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Add custom node error:', error);
return new Response(JSON.stringify({
success: false,
error: error.message,
details: '添加节点时发生错误'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 检测是否为Base64编码
function isBase64Encoded(str) {
// Base64字符串通常只包含A-Z, a-z, 0-9, +, /, = 字符
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
// 长度必须是4的倍数
return base64Regex.test(str) && str.length % 4 === 0 && str.length > 20;
}

// 检测节点是否重复
function isNodeDuplicate(config, existingNodes) {
// 标准化配置字符串进行比较
const normalizeConfig = (config) => {
// 移除可能的空白字符和换行符
return config.trim().replace(/\s+/g, '');
};

const normalizedNewConfig = normalizeConfig(config);

// 检查是否与现有节点重复
return existingNodes.some(node => {
const normalizedExistingConfig = normalizeConfig(node.config);
return normalizedExistingConfig === normalizedNewConfig;
});
}

// 处理单个节点配置
function processNodeConfig(config, existingNodes, newNodes) {
// 检查是否重复
if (isNodeDuplicate(config, existingNodes)) {
console.log('Duplicate node detected:', config);
return null; // 返回null表示跳过重复节点
}

// 提取节点名称(从#后面或配置中提取)
let nodeName = '';
if (config.includes('#')) {
const namePart = config.split('#').pop().trim();
// 解码URL编码的中文字符
try {
nodeName = decodeURIComponent(namePart);
} catch (e) {
nodeName = namePart; // 如果解码失败,使用原始字符串
}
} else if (config.includes('ps=')) {
// 对于vmess链接,尝试从ps参数提取名称
const psMatch = config.match(/ps=([^&]+)/);
if (psMatch) {
try {
nodeName = decodeURIComponent(psMatch[1]);
} catch (e) {
nodeName = psMatch[1]; // 如果解码失败,使用原始字符串
}
}
} else if (config.includes('remarks=')) {
// 对于其他协议,尝试从remarks参数提取名称
const remarksMatch = config.match(/remarks=([^&]+)/);
if (remarksMatch) {
try {
nodeName = decodeURIComponent(remarksMatch[1]);
} catch (e) {
nodeName = remarksMatch[1]; // 如果解码失败,使用原始字符串
}
}
}

// 如果没有提取到名称,使用默认名称
if (!nodeName) {
nodeName = 节点 ${existingNodes.length + newNodes.length + 1};
}

const newNode = {
id: (Date.now() + Math.random()).toString(),
name: nodeName,
config: config,
createdAt: new Date().toISOString()
};

return newNode;
}

// 删除自建节点
async function deleteCustomNode(id) {
try {
console.log('Deleting custom node:', id);

if (!id) {
return new Response(JSON.stringify({
success: false,
error: '节点ID不能为空'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}

const existingData = await NODESKV.get(KVKEYS.CUSTOM_NODES);
const nodes = existingData ? JSON.parse(existingData) : [];

console.log('Current nodes count:', nodes.length);

const originalLength = nodes.length;
const filteredNodes = nodes.filter(node => node.id !== id);

if (filteredNodes.length === originalLength) {
return new Response(JSON.stringify({
success: false,
error: '未找到要删除的节点'
}), {
status: 404,
headers: { 'Content-Type': 'application/json' }
});
}

console.log('Filtered nodes count:', filteredNodes.length);

await NODESKV.put(KVKEYS.CUSTOM_NODES, JSON.stringify(filteredNodes));

return new Response(JSON.stringify({
success: true,
message: '节点删除成功'
}), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Delete custom node error:', error);
return new Response(JSON.stringify({
success: false,
error: error.message,
details: '删除节点时发生错误'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 更新自建节点
async function updateCustomNode(id, data) {
try {
console.log('Updating custom node:', id, data);

// 验证输入数据
if (!data.name || !data.config) {
return new Response(JSON.stringify({
success: false,
error: '节点名称和配置不能为空'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}

const existingData = await NODESKV.get(KVKEYS.CUSTOM_NODES);
const nodes = existingData ? JSON.parse(existingData) : [];

console.log('Current nodes count:', nodes.length);

// 查找要更新的节点
const nodeIndex = nodes.findIndex(node => node.id === id);

if (nodeIndex === -1) {
return new Response(JSON.stringify({
success: false,
error: '未找到要更新的节点'
}), {
status: 404,
headers: { 'Content-Type': 'application/json' }
});
}

// 更新节点信息
nodes[nodeIndex].name = data.name;
nodes[nodeIndex].config = data.config;
nodes[nodeIndex].updatedAt = new Date().toISOString();

console.log('Updated node:', nodes[nodeIndex]);

await NODESKV.put(KVKEYS.CUSTOM_NODES, JSON.stringify(nodes));

return new Response(JSON.stringify({
success: true,
message: '节点更新成功'
}), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Update custom node error:', error);
return new Response(JSON.stringify({
success: false,
error: error.message,
details: '更新节点时发生错误'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 批量删除自建节点
async function batchDeleteCustomNodes(data) {
try {
console.log('Batch deleting custom nodes:', data);

// 验证输入数据
if (!data.ids || !Array.isArray(data.ids) || data.ids.length === 0) {
return new Response(JSON.stringify({
success: false,
error: '节点ID列表不能为空'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}

const existingData = await NODESKV.get(KVKEYS.CUSTOM_NODES);
const nodes = existingData ? JSON.parse(existingData) : [];

console.log('Current nodes count:', nodes.length);
console.log('Nodes to delete:', data.ids);

const originalLength = nodes.length;
const filteredNodes = nodes.filter(node => !data.ids.includes(node.id));
const deletedCount = originalLength - filteredNodes.length;

console.log('Filtered nodes count:', filteredNodes.length);
console.log('Deleted count:', deletedCount);

if (deletedCount === 0) {
return new Response(JSON.stringify({
success: false,
error: '未找到要删除的节点'
}), {
status: 404,
headers: { 'Content-Type': 'application/json' }
});
}

await NODESKV.put(KVKEYS.CUSTOM_NODES, JSON.stringify(filteredNodes));

return new Response(JSON.stringify({
success: true,
deletedCount: deletedCount,
message: 成功删除 ${deletedCount} 个节点
}), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Batch delete custom nodes error:', error);
return new Response(JSON.stringify({
success: false,
error: error.message,
details: '批量删除节点时发生错误'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 获取机场订阅
async function getSubscriptions() {
try {
const data = await NODESKV.get(KVKEYS.SUBSCRIPTION_URLS);
const subscriptions = data ? JSON.parse(data) : [];
return new Response(JSON.stringify(subscriptions), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(JSON.stringify([]), {
headers: { 'Content-Type': 'application/json' }
});
}
}

// 添加机场订阅
async function addSubscription(data) {
try {
console.log('Adding subscription:', data);

// 验证输入数据
if (!data.name || !data.url) {
return new Response(JSON.stringify({
success: false,
error: '订阅名称和链接不能为空'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}

// 验证URL格式
try {
new URL(data.url);
} catch (urlError) {
return new Response(JSON.stringify({
success: false,
error: '订阅链接格式不正确'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}

const existingData = await NODESKV.get(KVKEYS.SUBSCRIPTION_URLS);
console.log('Existing subscriptions:', existingData);

const subscriptions = existingData ? JSON.parse(existingData) : [];
console.log('Current subscriptions count:', subscriptions.length);

const newSubscription = {
id: Date.now().toString(),
name: data.name,
url: data.url,
createdAt: new Date().toISOString()
};

subscriptions.push(newSubscription);
console.log('New subscriptions count:', subscriptions.length);

const putResult = await NODESKV.put(KVKEYS.SUBSCRIPTION_URLS, JSON.stringify(subscriptions));
console.log('Put result:', putResult);

return new Response(JSON.stringify({
success: true,
id: newSubscription.id,
message: '订阅添加成功'
}), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Add subscription error:', error);
return new Response(JSON.stringify({
success: false,
error: error.message,
details: '添加订阅时发生错误'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 删除机场订阅
async function deleteSubscription(id) {
try {
console.log('Deleting subscription:', id);

if (!id) {
return new Response(JSON.stringify({
success: false,
error: '订阅ID不能为空'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}

const existingData = await NODESKV.get(KVKEYS.SUBSCRIPTION_URLS);
const subscriptions = existingData ? JSON.parse(existingData) : [];

console.log('Current subscriptions count:', subscriptions.length);

const originalLength = subscriptions.length;
const filteredSubscriptions = subscriptions.filter(sub => sub.id !== id);

if (filteredSubscriptions.length === originalLength) {
return new Response(JSON.stringify({
success: false,
error: '未找到要删除的订阅'
}), {
status: 404,
headers: { 'Content-Type': 'application/json' }
});
}

console.log('Filtered subscriptions count:', filteredSubscriptions.length);

await NODESKV.put(KVKEYS.SUBSCRIPTION_URLS, JSON.stringify(filteredSubscriptions));

return new Response(JSON.stringify({
success: true,
message: '订阅删除成功'
}), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Delete subscription error:', error);
return new Response(JSON.stringify({
success: false,
error: error.message,
details: '删除订阅时发生错误'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 更新机场订阅
async function updateSubscription(id, data) {
try {
console.log('Updating subscription:', id, data);

// 验证输入数据
if (!data.name || !data.url) {
return new Response(JSON.stringify({
success: false,
error: '订阅名称和链接不能为空'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}

// 验证URL格式
try {
new URL(data.url);
} catch (urlError) {
return new Response(JSON.stringify({
success: false,
error: '订阅链接格式不正确'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}

const existingData = await NODESKV.get(KVKEYS.SUBSCRIPTION_URLS);
const subscriptions = existingData ? JSON.parse(existingData) : [];

console.log('Current subscriptions count:', subscriptions.length);

// 查找要更新的订阅
const subscriptionIndex = subscriptions.findIndex(sub => sub.id === id);

if (subscriptionIndex === -1) {
return new Response(JSON.stringify({
success: false,
error: '未找到要更新的订阅'
}), {
status: 404,
headers: { 'Content-Type': 'application/json' }
});
}

// 更新订阅信息
subscriptions[subscriptionIndex].name = data.name;
subscriptions[subscriptionIndex].url = data.url;
subscriptions[subscriptionIndex].updatedAt = new Date().toISOString();

console.log('Updated subscription:', subscriptions[subscriptionIndex]);

await NODESKV.put(KVKEYS.SUBSCRIPTION_URLS, JSON.stringify(subscriptions));

return new Response(JSON.stringify({
success: true,
message: '订阅更新成功'
}), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Update subscription error:', error);
return new Response(JSON.stringify({
success: false,
error: error.message,
details: '更新订阅时发生错误'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}

// 修改后的订阅处理函数
async function handleSubscription(request, tag) {
let req_data = "";

// 从KV获取自建节点
try {
const customNodesData = await NODESKV.get(KVKEYS.CUSTOM_NODES);
if (customNodesData) {
const customNodes = JSON.parse(customNodesData);
customNodes.forEach(node => {
req_data += node.config + "\n";
});
}
} catch (error) {
console.error('获取自建节点失败:', error);
}

// 如果请求包含机场标签,获取机场订阅
if (tag === '9527abc-jichang') {
try {
const subscriptionsData = await NODESKV.get(KVKEYS.SUBSCRIPTION_URLS);
if (subscriptionsData) {
const subscriptions = JSON.parse(subscriptionsData);
const urls = subscriptions.map(sub => sub.url);

const responses = await Promise.all(urls.map(url => fetch(url)));

for (const response of responses) {
if (response.ok) {
const content = await response.text();
req_data += atob(content);
}
}
}
} catch (error) {
console.error('获取机场订阅失败:', error);
}
}

await sendMessage("#访问信息", request.headers.get('CF-Connecting-IP'), Tag: ${tag});
return new Response(btoa(req_data));
}

// 代码参考:
async function sendMessage(type, ip, add_data = "") {
const OPT = {
BotToken: tgbottoken, // Telegram Bot API
ChatID: tgchatid, // User 或者 ChatID,电报用户名
}

let msg = "";

const response = await fetch(http://ip-api.com/json/${ip});
if (response.status == 200) { // 查询 IP 来源信息,使用方法参考:https://ip-api.com/docs/api:json
const ipInfo = await response.json();
msg = ${type}\nIP: ${ip}\nCountry: ${ipInfo.country}\nCity: ${ipInfo.city}\n${add_data};
} else {
msg = ${type}\nIP: ${ip}\n${add_data};
}

let url = "https://api.telegram.org/";
url += "bot" + OPT.BotToken + "/sendMessage?";
url += "chat_id=" + OPT.ChatID + "&";
url += "text=" + encodeURIComponent(msg);

return fetch(url, {
method: 'get',
headers: {
'Accept': 'text/html,application/xhtml+xml,application/xml;',
'Accept-Encoding': 'gzip, deflate, br',
'User-Agent': 'Mozilla/5.0 Chrome/90.0.4430.72'
}
});
}