From 355c71407c26726668e5d4eca8ef4e385cce6626 Mon Sep 17 00:00:00 2001 From: violettools Date: Sat, 24 Jan 2026 13:01:25 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8C=96=E5=8A=A0=E5=AF=86=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=EF=BC=8C=E7=A7=BB=E9=99=A4Unicode=E6=B7=B7=E6=B7=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- add_encryption.js | 121 ++ build_encrypted.js | 224 ++++ deep_obfuscate.js | 97 ++ translate.js | 393 +++++++ workers_encrypted.js | 2446 ++++++++++++++++++++++++++++++++++++++++ workers_translated.js | 2426 +++++++++++++++++++++++++++++++++++++++ x27cn-github/worker.js | 1 + x27cn-pages/_worker.js | 1 + x27cn-wrangler.toml | 19 + x27cn_encrypt.js | 54 + 破皮版workers.js | 2 +- 11 files changed, 5783 insertions(+), 1 deletion(-) create mode 100644 add_encryption.js create mode 100644 build_encrypted.js create mode 100644 deep_obfuscate.js create mode 100644 translate.js create mode 100644 workers_encrypted.js create mode 100644 workers_translated.js create mode 100644 x27cn-github/worker.js create mode 100644 x27cn-pages/_worker.js create mode 100644 x27cn-wrangler.toml create mode 100644 x27cn_encrypt.js diff --git a/add_encryption.js b/add_encryption.js new file mode 100644 index 0000000..5631ec6 --- /dev/null +++ b/add_encryption.js @@ -0,0 +1,121 @@ +const fs = require('fs'); + +// 读取翻译后的代码 +let code = fs.readFileSync('workers_translated.js', 'utf8'); + +// ======================== 步骤1: 添加x27cn加密函数 ======================== +const x27cnFunctions = ` +const X27CN_KEY='x27cn2026'; +function x27cnEnc(t,k=X27CN_KEY){if(!t)return'';const kb=new TextEncoder().encode(k),tb=new TextEncoder().encode(t),r=new Uint8Array(tb.length);for(let i=0;i>5))&0xFF;b=(b+i)&0xFF;r[i]=b}return Array.from(r).map(b=>b.toString(16).padStart(2,'0')).join('')} +`; + +// 在import语句后添加加密函数 +code = code.replace( + `import { connect } from "cloudflare:sockets";`, + `import { connect } from "cloudflare:sockets";${x27cnFunctions}` +); + +// ======================== 步骤2: 修改根路径JSON响应 ======================== +// 使用更精确的多行匹配 +code = code.replace( + /return new Response\(JSON\.stringify\(\{\s*status:\s*'online',\s*version:\s*'1\.8\.7',\s*colo:\s*colo,\s*host:\s*url\.hostname,\s*uuid:\s*userID,\s*vless:\s*vlessLink,\s*two_proxy:\s*twoProxy\s*\|\|\s*null\s*\},\s*null,\s*2\),\s*\{\s*headers:\s*\{\s*'Content-Type':\s*'application\/json',\s*'Access-Control-Allow-Origin':\s*'\*'\s*\}\s*\}\);/g, + `return new Response(JSON.stringify({ + s: 'ok', + v: '1.8.7', + c: colo, + h: url.hostname, + enc: true, + d: x27cnEnc(JSON.stringify({u: userID, l: vlessLink, t: twoProxy || null})) + }, null, 2), { + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } + });` +); + +// ======================== 步骤3: 检查是否成功 ======================== +if (code.includes('enc: true')) { + console.log('Root response encrypted successfully!'); +} else { + console.log('WARNING: Root response not encrypted, trying alternative method...'); + + // 备用方法:按行替换 + const lines = code.split('\n'); + let inRootResponse = false; + let braceCount = 0; + let startLine = -1; + + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes("status: 'online'")) { + // 往前找到 return new Response + for (let j = i - 1; j >= 0 && j > i - 5; j--) { + if (lines[j].includes('return new Response(JSON.stringify({')) { + startLine = j; + inRootResponse = true; + braceCount = 1; + break; + } + } + } + + if (inRootResponse && startLine !== -1) { + // 计算花括号 + for (const ch of lines[i]) { + if (ch === '{') braceCount++; + if (ch === '}') braceCount--; + } + + if (braceCount === 0 && lines[i].includes('});')) { + // 找到结束位置,替换整个响应块 + const newResponse = ` return new Response(JSON.stringify({ + s: 'ok', + v: '1.8.7', + c: colo, + h: url.hostname, + enc: true, + d: x27cnEnc(JSON.stringify({u: userID, l: vlessLink, t: twoProxy || null})) + }, null, 2), { + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } + });`; + + // 删除原有行 + lines.splice(startLine, i - startLine + 1, newResponse); + console.log('Replaced using alternative method!'); + break; + } + } + } + + code = lines.join('\n'); +} + +// ======================== 步骤4: 加密api/uuid和api/config中的uuid ======================== +// 替换 configResponse.uuid = userID; +code = code.replace( + /configResponse\.uuid\s*=\s*userID;/g, + `configResponse.d = x27cnEnc(userID);` +); + +// ======================== 步骤5: 修改LINK生成(config中的vless链接) ======================== +code = code.replace( + /config_JSON\.LINK\s*=\s*`\$\{config_JSON\.protocolType\}/g, + `config_JSON.LINK = x27cnEnc(\`\${config_JSON.protocolType}` +); + +// 添加结尾括号 +code = code.replace( + /\$\{config_JSON\.skipCertVerify\s*\?\s*"&insecure=1&allowInsecure=1"\s*:\s*""\}#\$\{encodeURIComponent\(config_JSON\.subGenerator\.SUBNAME\)\}`;/g, + `\${config_JSON.skipCertVerify ? "&insecure=1&allowInsecure=1" : ""}#\${encodeURIComponent(config_JSON.subGenerator.SUBNAME)}\`);` +); + +// ======================== 步骤6: 保存 ======================== +fs.writeFileSync('workers_encrypted.js', code, 'utf8'); +console.log('Saved to workers_encrypted.js'); + +// 验证 +if (code.includes('x27cnEnc')) { + console.log('✓ x27cnEnc function present'); +} +if (code.includes('enc: true')) { + console.log('✓ Root response encrypted'); +} else { + console.log('✗ Root response NOT encrypted - manual check needed'); +} diff --git a/build_encrypted.js b/build_encrypted.js new file mode 100644 index 0000000..4ebcbc8 --- /dev/null +++ b/build_encrypted.js @@ -0,0 +1,224 @@ +const fs = require('fs'); + +// 读取原始文件 +let code = fs.readFileSync('workers.js', 'utf8'); + +// ======================== 步骤1: 添加x27cn加密函数 ======================== +const x27cnFunctions = ` +// x27cn encryption +const X27CN_KEY = 'x27cn2026'; +function x27cnEnc(t, k = X27CN_KEY) { + if (!t) return ''; + const kb = new TextEncoder().encode(k); + const tb = new TextEncoder().encode(t); + const r = new Uint8Array(tb.length); + for (let i = 0; i < tb.length; i++) { + let b = tb[i] ^ kb[i % kb.length]; + b = ((b << 3) | (b >> 5)) & 0xFF; + b = (b + i) & 0xFF; + r[i] = b; + } + return Array.from(r).map(b => b.toString(16).padStart(2, '0')).join(''); +} +`; + +// 在import语句后添加加密函数 +code = code.replace( + `import { connect } from "cloudflare:sockets";`, + `import { connect } from "cloudflare:sockets";${x27cnFunctions}` +); + +// ======================== 步骤2: 修改根路径JSON响应 ======================== +// 找到返回vless链接的地方,加密敏感信息 +code = code.replace( + /return new Response\(JSON\.stringify\(\{\s*status: 'online',\s*version: '1\.8\.7',\s*colo: colo,\s*host: url\.hostname,\s*uuid: userID,\s*vless: vlessLink,\s*two_proxy: twoProxy \|\| null\s*\}, null, 2\)/g, + `return new Response(JSON.stringify({ + status: 'online', + version: '1.8.7', + colo: colo, + host: url.hostname, + encrypted: true, + data: x27cnEnc(JSON.stringify({uuid: userID, vless: vlessLink, two_proxy: twoProxy || null})), + decrypt_url: 'https://gist.github.com/x27cn/decrypt' + }, null, 2)` +); + +// ======================== 步骤3: 修改api/config响应 ======================== +// 加密uuid和vless_path +code = code.replace( + /configResponse\.uuid = userID;/g, + `configResponse.data = x27cnEnc(userID);` +); + +code = code.replace( + /configResponse\.vless_path = twoProxy/g, + `configResponse.path_data = x27cnEnc(twoProxy` +); + +// ======================== 步骤4: 删除中文注释 ======================== +code = code.split('\n').map(line => { + if (/^\s*\/\/.*[\u4e00-\u9fff]/.test(line)) return ''; + if (/\/\/.*[\u4e00-\u9fff]/.test(line)) { + return line.replace(/\/\/.*[\u4e00-\u9fff].*$/, ''); + } + return line; +}).join('\n'); + +// 删除多行注释 +code = code.replace(/\/\*[\s\S]*?[\u4e00-\u9fff][\s\S]*?\*\//g, ''); + +// ======================== 步骤5: 翻译中文变量名 ======================== +const translations = { + // 敏感变量名和字符串替换 + 'cfspiderPath': 'apiPath', + 'cfspider-public-00000000-0000': 'pub-node-00000000-0000', + 'x-cfspider-header-': 'x-custom-header-', + 'X-CFspider-Version': 'X-Worker-Version', + 'X-CFspider-TwoProxy': 'X-Two-Proxy', + 'CFspider/1.8.3': 'Mozilla/5.0', + 'cfspider.get': 'client.get', + 'CFspider-': 'Node-', + '#CFspider': '#Node', + + // 变量名翻译 + '反代IP': 'proxyIP', + '启用SOCKS5反代': 'enableSOCKS5Proxy', + '启用SOCKS5全局反代': 'enableGlobalSOCKS5', + '我的SOCKS5账号': 'mySOCKS5Account', + '缓存反代IP': 'cachedProxyIP', + '缓存反代解析数组': 'cachedProxyArray', + '缓存反代数组索引': 'cachedProxyIndex', + '启用反代兜底': 'enableProxyFallback', + 'SOCKS5白名单': 'socks5Whitelist', + 'Pages静态页面': 'pagesStaticUrl', + '管理员密码': 'adminPassword', + '加密秘钥': 'encryptKey', + '访问IP': 'clientIP', + '整理成数组': 'parseToArray', + '优选订阅生成': 'subGenerator', + '本地IP库': 'localIPPool', + '随机数量': 'randomCount', + '指定端口': 'specifiedPort', + '随机IP': 'randomIP', + '订阅转换配置': 'subConverterConfig', + '反代': 'reverseProxy', + '全局': 'globalMode', + '账号': 'account', + '白名单': 'whitelist', + '传输协议': 'transport', + '跳过证书验证': 'skipCertVerify', + '启用0RTT': 'enable0RTT', + 'TLS分片': 'tlsFragment', + '随机路径': 'randomPath', + '本地': 'local', + '协议类型': 'protocolType', + '区分大小写访问': 'caseSensitivePath', + '输入密码': 'inputPassword', + '日志内容': 'logContent', + '待验证优选': 'pendingOptimal', + '请求优选': 'requestOptimal', + '完整优选': 'fullOptimal', + '其他节点': 'otherNodes', + '节点': 'node', + '内容': 'content', + '优选': 'optimal', + '保存自定义': 'saveCustom', + '生成': 'gen', + '订阅': 'sub', + '地址备注分离': 'addrRemarkSplit', + '订阅行列表': 'subLineList', + '行内容': 'lineContent', + '地址匹配': 'addrMatch', + '地址端口': 'addrPort', + '备注': 'remark', + '原始地址': 'origAddr', + '节点地址': 'nodeAddr', + '节点端口': 'nodePort', + '节点备注': 'nodeRemark', + '订阅转换': 'subConvert', + '订阅配置文件热补丁': 'subConfigHotfix', + '批量替换': 'batchReplace', + '获取': 'get', + '处理': 'handle', + '请求': 'request', + '伪装页': 'fakePage', + '新请求头': 'newHeaders', + '响应内容': 'respContent', + '响应': 'resp', + '目标': 'target', + '所有': 'all', + '数组': 'arr', + '兜底': 'fallback', + '地址': 'addr', + '端口': 'port', + '索引': 'idx', + '错误': 'err', + '代理': 'proxy', + '验证': 'verify', + '原始': 'raw', + '启用': 'enable', + '当前': 'curr', + '日志数组': 'logArr', + '编码器': 'encoder', + '第一次哈希': 'hash1', + '第二次哈希': 'hash2', + '常用': 'common', + '目录': 'dir', + '随机数': 'randNum', + '字符集': 'charset', + '重置配置': 'resetCfg', + '初始化': 'init', + '加载': 'load', + '官方优选': 'officialOptimal', + '默认端口': 'defPort', + '超时': 'timeout', + '国家': 'country', + '城市': 'city', + '解析': 'parse', + '总计': 'totalCnt', + '查询': 'query', + '记录': 'record', + '代理协议': 'proxyProto', + '格式化': 'format', + '随机字符串': 'randStr', + '访问': 'visit', + '失败': 'failed', + '荷兰节点': 'nlNode', + '香港节点': 'hkNode', + '未知IP': 'unknownIP', + '双层代理': 'twoProxy', + '下载速度': 'dlSpeed', + '数据中心': 'dataCenter', + '延迟': 'latency', + '移动优选': 'CMCCOptimal', + '联通优选': 'CUOptimal', + '电信优选': 'CTOptimal', + 'CF优选': 'CFOptimal', + '的': '', + '了': '', + '个': '' +}; + +// 按长度排序 +const sortedKeys = Object.keys(translations).sort((a, b) => b.length - a.length); +for (const cn of sortedKeys) { + code = code.split(cn).join(translations[cn]); +} + +// 删除空行 +code = code.replace(/\n{3,}/g, '\n\n'); + +// 保存翻译后的代码 +fs.writeFileSync('workers_encrypted.js', code, 'utf8'); +console.log('Step 1: Translation complete!'); + +// 检查剩余中文 +const remaining = code.match(/[\u4e00-\u9fff]+/g); +if (remaining) { + const unique = [...new Set(remaining)]; + console.log(`Remaining Chinese: ${unique.length}`); + unique.slice(0, 10).forEach(c => console.log(' ' + c)); +} else { + console.log('No Chinese characters!'); +} + diff --git a/deep_obfuscate.js b/deep_obfuscate.js new file mode 100644 index 0000000..b069cb0 --- /dev/null +++ b/deep_obfuscate.js @@ -0,0 +1,97 @@ +const fs = require('fs'); + +// 读取翻译后但未混淆的代码 +let code = fs.readFileSync('workers_translated.js', 'utf8'); + +// 创建一个函数来生成base64解码表达式 +function b64(str) { + return `atob("${Buffer.from(str).toString('base64')}")`; +} + +// 敏感字符串替换映射 - 在代码混淆前替换 +const sensitiveReplacements = { + // 动态拼接的字符串 + "'.proxyip.cmliussss.net'": `(${b64('.proxyip.cmliussss.net')})`, + '".proxyip.cmliussss.net"': `(${b64('.proxyip.cmliussss.net')})`, + "'.proxyip.'": `(${b64('.proxyip.')})`, + '".proxyip."': `(${b64('.proxyip.')})`, + "'doh.cmliussss.com'": b64('doh.cmliussss.com'), + '"doh.cmliussss.com"': b64('doh.cmliussss.com'), + 'https://doh.cmliussss.com/CMLiussss': '"+atob("aHR0cHM6Ly9kb2guY21saXVzc3NzLmNvbS9DTUxpdXNzc3M=")+"', + 'useCfspiderGetWithTwoProxy': 'useClientWithTwoProxy', + 'notSupportedViaProxyAPI': 'notSupportedViaAPI', + + // 协议相关 + "'vless://'": `(${b64('vless://')})`, + '"vless://"': `(${b64('vless://')})`, + "'trojan://'": `(${b64('trojan://')})`, + '"trojan://"': `(${b64('trojan://')})`, + "'vless'": b64('vless'), + '"vless"': b64('vless'), + "'trojan'": b64('trojan'), + '"trojan"': b64('trojan'), + + // 域名相关 + "'proxyip.cmliussss.net'": b64('proxyip.cmliussss.net'), + '"proxyip.cmliussss.net"': b64('proxyip.cmliussss.net'), + "'proxyip.cfspider.com'": b64('proxyip.cfspider.com'), + '"proxyip.cfspider.com"': b64('proxyip.cfspider.com'), + "'.PrOxYIp.CmLiUsSsS.nEt'": b64('.PrOxYIp.CmLiUsSsS.nEt'), + '".PrOxYIp.CmLiUsSsS.nEt"': b64('.PrOxYIp.CmLiUsSsS.nEt'), + + // 项目名称 + "'cfspider-public'": b64('cfspider-public'), + '"cfspider-public"': b64('cfspider-public'), + "'cfspider-default-key'": b64('cfspider-default-key'), + '"cfspider-default-key"': b64('cfspider-default-key'), + "'CFspider'": b64('CFspider'), + '"CFspider"': b64('CFspider'), + "'CFspider-'": `(${b64('CFspider-')})`, + '"CFspider-"': `(${b64('CFspider-')})`, + "'cfspider'": b64('cfspider'), + '"cfspider"': b64('cfspider'), + "'edgetunnel'": b64('edgetunnel'), + '"edgetunnel"': b64('edgetunnel'), + + // URL相关 + "'https://doh.cmliussss.net/CMLiussss'": b64('https://doh.cmliussss.net/CMLiussss'), + '"https://doh.cmliussss.net/CMLiussss"': b64('https://doh.cmliussss.net/CMLiussss'), + "'https://edt-pages.github.io'": b64('https://edt-pages.github.io'), + '"https://edt-pages.github.io"': b64('https://edt-pages.github.io'), + "'https://SUBAPI.cmliussss.net'": b64('https://SUBAPI.cmliussss.net'), + '"https://SUBAPI.cmliussss.net"': b64('https://SUBAPI.cmliussss.net'), + "'https://raw.githubusercontent.com/cmliu/ACL4SSR/refs/heads/main/Clash/config/ACL4SSR_Online_Mini_MultiMode_CF.ini'": b64('https://raw.githubusercontent.com/cmliu/ACL4SSR/refs/heads/main/Clash/config/ACL4SSR_Online_Mini_MultiMode_CF.ini'), + '"https://raw.githubusercontent.com/cmliu/ACL4SSR/refs/heads/main/Clash/config/ACL4SSR_Online_Mini_MultiMode_CF.ini"': b64('https://raw.githubusercontent.com/cmliu/ACL4SSR/refs/heads/main/Clash/config/ACL4SSR_Online_Mini_MultiMode_CF.ini'), + + // 其他敏感字符串 + "'cmliu'": b64('cmliu'), + '"cmliu"': b64('cmliu'), + "'/proxyip'": b64('/proxyip'), + '"/proxyip"': b64('/proxyip'), + "'proxyip='": `(${b64('proxyip=')})`, + '"proxyip="': `(${b64('proxyip=')})`, + "'/proxyip='": `(${b64('/proxyip=')})`, + '"/proxyip="': `(${b64('/proxyip=')})`, +}; + +// 按长度排序(从长到短),避免部分替换 +const sortedKeys = Object.keys(sensitiveReplacements).sort((a, b) => b.length - a.length); + +// 执行替换 +for (const key of sortedKeys) { + code = code.split(key).join(sensitiveReplacements[key]); +} + +// 保存预处理后的代码 +fs.writeFileSync('workers_preprocessed.js', code, 'utf8'); +console.log('Preprocessed! Now run terser...'); + +// 检查剩余敏感字符串 +const patterns = ['vless://', 'trojan://', 'cfspider', 'cmliussss', 'edgetunnel', 'proxyip.c']; +const remaining = patterns.filter(p => code.toLowerCase().includes(p.toLowerCase())); +if (remaining.length > 0) { + console.log('Remaining sensitive strings:', remaining.join(', ')); +} else { + console.log('All sensitive strings encoded!'); +} + diff --git a/translate.js b/translate.js new file mode 100644 index 0000000..d298de2 --- /dev/null +++ b/translate.js @@ -0,0 +1,393 @@ +const fs = require('fs'); + +// 读取原始文件 +let code = fs.readFileSync('workers.js', 'utf8'); + +// 第一步:删除所有包含中文的注释行 +code = code.split('\n').map(line => { + // 如果整行是注释且包含中文,删除 + if (/^\s*\/\/.*[\u4e00-\u9fff]/.test(line)) return ''; + // 如果行尾有包含中文的注释,删除注释部分 + if (/\/\/.*[\u4e00-\u9fff]/.test(line)) { + return line.replace(/\/\/.*[\u4e00-\u9fff].*$/, ''); + } + return line; +}).join('\n'); + +// 删除多行注释中包含中文的 +code = code.replace(/\/\*[\s\S]*?[\u4e00-\u9fff][\s\S]*?\*\//g, ''); + +// 中文到英文的完整映射表 - 全部使用驼峰式(无空格) +const translations = { + // 敏感变量名和字符串替换 + 'cfspiderPath': 'apiPath', + 'cfspider-public-00000000-0000': 'pub-node-00000000-0000', + 'x-cfspider-header-': 'x-custom-header-', + 'X-CFspider-Version': 'X-Worker-Version', + 'X-CFspider-TwoProxy': 'X-Two-Proxy', + 'CFspider/1.8.3': 'Mozilla/5.0', + 'cfspider.get': 'client.get', + 'CFspider-': 'Node-', + '#CFspider': '#Node', + + // 原有翻译 + // 变量名翻译 + '反代IP': 'proxyIP', + '启用SOCKS5反代': 'enableSOCKS5Proxy', + '启用SOCKS5全局反代': 'enableGlobalSOCKS5', + '我的SOCKS5账号': 'mySOCKS5Account', + '缓存反代IP': 'cachedProxyIP', + '缓存反代解析数组': 'cachedProxyArray', + '缓存反代数组索引': 'cachedProxyIndex', + '启用反代兜底': 'enableProxyFallback', + 'SOCKS5白名单': 'socks5Whitelist', + 'Pages静态页面': 'pagesStaticUrl', + '管理员密码': 'adminPassword', + '加密秘钥': 'encryptKey', + '访问IP': 'clientIP', + '整理成数组': 'parseToArray', + + // 配置对象属性名 + '优选订阅生成': 'subGenerator', + '本地IP库': 'localIPPool', + '随机数量': 'randomCount', + '指定端口': 'specifiedPort', + '随机IP': 'randomIP', + '订阅转换配置': 'subConverterConfig', + '反代': 'reverseProxy', + '全局': 'globalMode', + '账号': 'account', + '白名单': 'whitelist', + '传输协议': 'transport', + '跳过证书验证': 'skipCertVerify', + '启用0RTT': 'enable0RTT', + 'TLS分片': 'tlsFragment', + '随机路径': 'randomPath', + '本地': 'local', + '协议类型': 'protocolType', + + // 新增翻译 + '区分大小写访问': 'caseSensitivePath', + '输入密码': 'inputPassword', + '日志内容': 'logContent', + '待验证优选': 'pendingOptimal', + '请求优选': 'requestOptimal', + '完整优选': 'fullOptimal', + '完整优选列表': 'fullOptimalList', + '其他节点': 'otherNodes', + '节点': 'node', + '内容': 'content', + '优选': 'optimal', + '检测代理响应': 'checkProxyResponse', + '可用性验证': 'availabilityCheck', + '请求日志记录': 'requestLogRecord', + '保存自定义': 'saveCustom', + '生成': 'gen', + '订阅': 'sub', + '元素': 'element', + '地址备注分离': 'addrRemarkSplit', + '合并其他节点数组': 'mergeOtherNodes', + '器': 'er', + '器返回': 'erReturn', + '订阅行列表': 'subLineList', + '行内容': 'lineContent', + '地址匹配': 'addrMatch', + '地址端口': 'addrPort', + '备注': 'remark', + '备注匹配': 'remarkMatch', + '原始地址': 'origAddr', + '节点地址': 'nodeAddr', + '节点端口': 'nodePort', + '节点备注': 'nodeRemark', + '订阅转换': 'subConvert', + '订阅配置文件热补丁': 'subConfigHotfix', + '批量替换': 'batchReplace', + '获取': 'get', + '处理': 'handle', + '请求': 'request', + '伪装页': 'fakePage', + '新请求头': 'newHeaders', + '响应内容': 'respContent', + '响应': 'resp', + '判断是否是木马': 'checkIfTrojan', + '解析木马请求': 'parseTrojanReq', + '解析魏烈思请求': 'parseVlessReq', + '转发': 'forward', + '目标': 'target', + '否': 'no', + '所有': 'all', + '数组': 'arr', + '兜底': 'fallback', + '数组索引': 'arrIndex', + '地址': 'addr', + '端口': 'port', + '索引': 'idx', + '错误': 'err', + '且未': 'andNot', + '代理': 'proxy', + '解析地址端口': 'parseAddrPort', + '验证': 'verify', + '原始': 'raw', + '启用': 'enable', + '每行内容': 'eachLine', + '输出内容': 'output', + '备改内容': 'backup', + '正确内容': 'correct', + '容量限制': 'capLimit', + '当前': 'curr', + '日志数组': 'logArr', + '现有日志': 'existLog', + '三十分钟前': 'thirtyMinAgo', + '戳': 'stamp', + '掩码敏感信息': 'maskSensitive', + '文本': 'txt', + '前缀长度': 'prefixLen', + '后缀长度': 'suffixLen', + '前缀': 'prefix', + '后缀': 'suffix', + '星号数量': 'starCount', + '编码器': 'encoder', + '第一次哈希': 'hash1', + '第一次哈希数组': 'hash1Arr', + '第一次十六进制': 'hex1', + '字节': 'byte', + '第二次哈希': 'hash2', + '第二次哈希数组': 'hash2Arr', + '第二次十六进制': 'hex2', + '常用': 'common', + '目录': 'dir', + '随机数': 'randNum', + '随机替换通配符': 'randReplaceWildcard', + '字符集': 'charset', + '每组数量': 'groupCnt', + '打乱后数组': 'shuffledArr', + '重置配置': 'resetCfg', + '初始化开始': 'initStart', + '默认配置': 'defCfg', + '初始化': 'init', + '加载': 'load', + '官方优选': 'officialOptimal', + '替换后的内容': 'replaced', + '地址数组': 'addrArr', + '默认端口': 'defPort', + '超时': 'timeout', + '订阅链接响应的明文': 'subLinkPlain', + '需要订阅转换订阅': 'needSubConvert', + '预处理订阅明文内容': 'preSubPlain', + '国家': 'country', + '城市': 'city', + '路参': 'pathParam', + '解析': 'parse', + '地址失败': 'addrFailed', + '如': 'like', + '总计': 'totalCnt', + '查询': 'query', + '记录': 'record', + '解析地址端口字符串': 'parseAddrPortStr', + '排序后数组': 'sortedArr', + '目标根': 'targetRoot', + '洗牌后': 'shuffled', + '代理协议': 'proxyProto', + '完整代理': 'fullProxy', + '格式化': 'format', + '随机字符串': 'randStr', + '访问': 'visit', + '失败': 'failed', + + // 函数和错误消息(全部驼峰式) + '荷兰节点': 'nlNode', + '香港节点': 'hkNode', + '未知IP': 'unknownIP', + '配置已保存': 'configSaved', + '配置已重置为默认值': 'configResetToDefault', + '配置不完整': 'incompleteConfig', + '配置重置失败': 'configResetFailed', + '失败原因': 'reason', + '保存配置失败': 'saveConfigFailed', + '保存自定义优选IP失败': 'saveCustomIPFailed', + '自定义优选IP已保存': 'customIPSaved', + '自定义IP已保存': 'customIPSaved', + '不支持的POST请求路径': 'unsupportedPOSTPath', + '重定向中': 'redirecting', + '请通过 Cloudflare Dashboard 或 wrangler.toml 设置 NEW_IP 环境变量': 'setNEWIPViaDashboard', + '请使用 Python cfspider.get() 配合 two_proxy 参数': 'useCfspiderGetWithTwoProxy', + '仅支持 HTTP 双层代理': 'onlyHTTPTwoProxySupported', + '不支持通过 /proxy API': 'notSupportedViaProxyAPI', + '隧道建立成功': 'tunnelEstablished', + '代理连接关闭': 'proxyClosed', + '无效的代理响应': 'invalidProxyResp', + '代理连接失败': 'proxyConnFailed', + '双层代理未配置': 'twoProxyNotConfigured', + '读取': 'read', + '出错': 'error', + '日志记录失败': 'logFailed', + '日志通知': 'logNotify', + '类型': 'type', + '位置': 'location', + '域名': 'domain', + '路径': 'path', + '时间': 'time', + '请求用量': 'requestUsage', + '统计结果': 'statResult', + '上限': 'limit', + '账户获取失败': 'accountGetFailed', + '未找到账户': 'accountNotFound', + '未找到账户数据': 'accountDataNotFound', + '查询失败': 'queryFailed', + '获取使用量错误': 'getUsageError', + '查询请求量失败': 'queryRequestFailed', + '验证优选API失败': 'verifyAPIFailed', + '缺少代理参数': 'missingProxyParam', + '无法连接到代理服务器': 'cannotConnectToProxy', + '关闭连接时出错': 'errorClosingConn', + + // 日志消息 + '尝试连接到': 'connectingTo', + '成功连接到': 'connectedTo', + '连接失败': 'connFailed', + '连接超时': 'connTimeout', + '尝试直连到': 'directConnTo', + '代理到': 'proxyTo', + '使用双层代理': 'usingTwoProxy', + '回退到默认方式': 'fallbackToDefault', + '目标站点': 'targetSite', + '随机种子': 'randSeed', + '解析完成': 'parseComplete', + '总数': 'total', + '个': '', + '读取缓存': 'readCache', + '启用 SOCKS5/HTTP 全局代理': 'enableGlobalProxy', + '所有反代连接失败': 'allProxyFailed', + '且未启用兜底': 'fallbackDisabled', + '连接终止': 'connTerminated', + '反代连接': 'proxyConn', + '反代解析': 'proxyParse', + '反代类型': 'proxyType', + '已启用': 'enabled', + '通过': 'via', + '连接到': 'connTo', + + // 订阅相关 + '优选订阅生成器异常': 'subGenError', + '订阅转换后端异常': 'subConverterError', + '订阅内容': 'subContent', + '不规范的IP格式已忽略': 'invalidIPIgnored', + + // 解析相关 + '解析William域名失败': 'parseWilliamFailed', + 'DoH查询失败': 'dohQueryFailed', + '无效的': 'invalid', + '地址格式': 'addrFormat', + '认证部分必须是': 'authMustBe', + '的形式': 'format', + '端口号必须是数字': 'portMustBeNumber', + '地址必须用方括号括起来': 'addrMustBeBracketed', + + // Singbox热补丁 + 'Singbox热补丁执行失败': 'singboxPatchFailed', + + // 优选标签 + '移动优选': 'CMCCOptimal', + '联通优选': 'CUOptimal', + '电信优选': 'CTOptimal', + 'CF优选': 'CFOptimal', + + // 密钥相关 + '勿动此默认密钥,有需求请自行通过添加变量KEY进行修改': 'doNotModifyDefaultKey', + + // 双层代理 + '双层代理': 'twoProxy', + '不支持通过': 'notSupportedVia', + '参数': 'param', + + // 下载速度等 + '下载速度': 'dlSpeed', + '数据中心': 'dataCenter', + '延迟': 'latency', + + // 单字符和短语 + '的': '', + '公开': 'public', + '返回完整': 'returnFull', + '配置': 'config', + '去掉': 'remove', + '快速': 'fast', + '登录页面和登录': 'loginPageAnd', + '后': 'after', + '管理页面': 'adminPage', + '量': 'amt', + '检查': 'check', + '为默认值': 'toDefault', + '操作': 'op', + '保存': 'save', + '保存到': 'saveTo', + '返回': 'return', + '配置文件': 'cfgFile', + '清除': 'clear', + '并跳转到登录页面': 'redirectToLogin', + '到期': 'expire', + '跳过空行': 'skipEmpty', + '这是': 'thisIs', + '行': 'line', + '提取': 'extract', + '或': 'or', + '可能带方括号': 'mayHaveBrackets', + '默认': 'def', + '默认为': 'defTo', + '本身': 'itself', + '列表': 'list', + '顶级属性的缩进': 'topLevelIndent', + '顶级属性缩进': 'topLevelIndent', + '强制使用现代方式': 'forceModern', + '移除': 'remove', + '因为已经改用': 'becauseChanged', + '了': '', + '果长度太短': 'ifTooShort', + '直接返回': 'directReturn', + '基于': 'basedOn', + '当': 'when', + '为': 'is', + '时生效': 'effective', + '数量': 'cnt', + '则使用': 'thenUse', + '内': 'in', + '更新': 'update', + '小时': 'hour', + '时强制将': 'forceChange', + '改为': 'changeTo', + '默认优先': 'defFirst', + '果明确指定': 'ifSpecified', + '系编码': 'encoding', + '优先尝试': 'tryFirst', + '追加': 'append', + '明文': 'plain', + '开头': 'start', + '带': 'with', + '无': 'none' +}; + +// 按长度排序(从长到短) +const sortedKeys = Object.keys(translations).sort((a, b) => b.length - a.length); + +// 执行替换 +for (const cn of sortedKeys) { + const en = translations[cn]; + code = code.split(cn).join(en); +} + +// 删除空行过多的情况 +code = code.replace(/\n{3,}/g, '\n\n'); + +// 保存翻译后的文件 +fs.writeFileSync('workers_translated.js', code, 'utf8'); +console.log('Translation complete! Saved to workers_translated.js'); + +// 检查是否还有剩余中文 +const remaining = code.match(/[\u4e00-\u9fff]+/g); +if (remaining) { + const unique = [...new Set(remaining)]; + console.log('\nRemaining Chinese characters (' + unique.length + '):'); + unique.forEach(c => console.log(' ' + c)); +} else { + console.log('\nNo remaining Chinese characters!'); +} diff --git a/workers_encrypted.js b/workers_encrypted.js new file mode 100644 index 0000000..602f902 --- /dev/null +++ b/workers_encrypted.js @@ -0,0 +1,2446 @@ +import { connect } from "cloudflare:sockets"; +const X27CN_KEY='x27cn2026'; +const X27_KAOMOJI=['(◕‿◕)','(。◕‿◕。)','(◠‿◠)','(✿◠‿◠)','(◡‿◡)','(◕ᴗ◕)','(◔‿◔)','(✧ω✧)','(◕◡◕)','(◠ᴗ◠)','♪(´▽`)','(◕‿◕✿)','(✿╹◡╹)','(◕ˇ∀ˇ◕)','(◔◡◔)']; +const X27_CHAOS=['龖','龘','靐','齉','齾','爨','灪','麤','鱻','驫','骉','羴','猋','蟲','贔','矗','飝','厵','靇','雥']; +function x27cnEnc(t,k=X27CN_KEY){ + if(!t)return''; + const kb=new TextEncoder().encode(k),tb=new TextEncoder().encode(t),r=new Uint8Array(tb.length); + for(let i=0;i>5))&0xFF; + b=(b+i)&0xFF; + r[i]=b; + } + const hex=Array.from(r).map(b=>b.toString(16).padStart(2,'0')).join(''); + let result=''; + const seed=tb.length; + for(let i=0;i0){ + const ki=(seed+i)%X27_KAOMOJI.length; + result+=X27_KAOMOJI[ki]; + } + if(i%16===0){ + const ci=(seed*2+i)%X27_CHAOS.length; + result+=X27_CHAOS[ci]; + } + } + const prefix=X27_KAOMOJI[seed%X27_KAOMOJI.length]+X27_CHAOS[seed%X27_CHAOS.length]; + const suffix=X27_CHAOS[(seed*3)%X27_CHAOS.length]+X27_KAOMOJI[(seed*2)%X27_KAOMOJI.length]; + return prefix+result+suffix; +} + +let config_JSON, proxyIP = '', enableSOCKS5Proxy = null, enableGlobalSOCKS5 = false, mySOCKS5Account = '', parsedSocks5Address = {}; +let cachedProxyIP, cachedProxyArray, cachedProxyIndex = 0, enableProxyFallback = true, ECH_DOH = 'https://doh.cmliussss.net/CMLiussss'; +let socks5Whitelist = ['*tapecontent.net', '*cloudatacdn.com', '*loadshare.org', '*cdn-centaurus.com', 'scholar.google.com']; +const pagesStaticUrl = 'https://edt-pages.github.io'; + +export default { + async fetch(request, env, ctx) { + const url = new URL(request.url); + const UA = request.headers.get('User-Agent') || 'null'; + const upgradeHeader = request.headers.get('Upgrade'); + + const PUBLIC_UUID = 'pub-node-00000000-0000'; // public UUID prefix + const adminPassword = env.ADMIN || env.admin || env.PASSWORD || env.password || env.pswd || env.TOKEN || env.KEY || env.UUID || env.uuid || 'cfspider-public'; + const encryptKey = env.KEY || 'cfspider-default-key'; + const userIDMD5 = await MD5MD5(adminPassword + encryptKey); + const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/; + const envUUID = env.UUID || env.uuid; + const userID = (envUUID && uuidRegex.test(envUUID)) ? envUUID.toLowerCase() : [userIDMD5.slice(0, 8), userIDMD5.slice(8, 12), '4' + userIDMD5.slice(13, 16), '8' + userIDMD5.slice(17, 20), userIDMD5.slice(20)].join('-'); + const hosts = env.HOST ? (await parseToArray(env.HOST)).map(h => h.toLowerCase().replace(/^https?:\/\//, '').split('/')[0].split(':')[0]) : [url.hostname]; + const host = hosts[0]; + if (env.PROXYIP) { + const proxyIPs = await parseToArray(env.PROXYIP); + proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; + enableProxyFallback = false; + } else proxyIP = (request.cf.colo + '.PrOxYIp.CmLiUsSsS.nEt').toLowerCase(); + const clientIP = request.headers.get('X-Real-IP') || request.headers.get('CF-Connecting-IP') || request.headers.get('X-Forwarded-For') || request.headers.get('True-Client-IP') || request.headers.get('Fly-Client-IP') || request.headers.get('X-Appengine-Remote-Addr') || request.headers.get('X-Forwarded-For') || request.headers.get('X-Real-IP') || request.headers.get('X-Cluster-Client-IP') || request.cf?.clientTcpRtt || 'unknownIP'; + if (env.GO2SOCKS5) socks5Whitelist = await parseToArray(env.GO2SOCKS5); + ECH_DOH = env.ECH_DOH || env.DOH || ECH_DOH; + if (!upgradeHeader || upgradeHeader !== 'websocket') { + if (url.protocol === 'http:') return Response.redirect(url.href.replace(`http://${url.hostname}`, `https://${url.hostname}`), 301); + + + const apiPath = url.pathname.slice(1).toLowerCase(); + const newIpEnabled = env.NEW_IP !== 'false' && env.NEW_IP !== '0'; + + const isDefaultUUID = !envUUID || !uuidRegex.test(envUUID); + + const twoProxy = env.TWO_PROXY || env.two_proxy || ''; + if (apiPath === '' || apiPath === '/') { + const colo = request.cf?.colo || 'UNKNOWN'; + const vlessPath = '/' + userID + (twoProxy ? '?two_proxy=' + encodeURIComponent(twoProxy) : ''); + const vlessLink = 'vless://' + userID + '@' + url.hostname + ':443?security=tls&type=ws&host=' + url.hostname + '&sni=' + url.hostname + '&path=' + encodeURIComponent(vlessPath) + '&encryption=none#Node-' + colo; + const fullData = {status: 'online', version: '1.8.7', colo: colo, host: url.hostname, uuid: userID, vless: vlessLink, two_proxy: twoProxy || null}; + return new Response(x27cnEnc(JSON.stringify(fullData)), { + headers: { 'Content-Type': 'text/plain', 'Access-Control-Allow-Origin': '*', 'X-Enc': 'x27cn' } + }); + } + + if (apiPath === 'api/proxyip') { + const colo = request.cf?.colo || 'UNKNOWN'; + const defaultProxyIp = (colo + '.proxyip.cmliussss.net').toLowerCase(); + const envProxyIp = env.PROXYIP || ''; + const proxyData = { + colo: colo, + default: defaultProxyIp, + hk: 'proxyip.cfspider.com', + env: envProxyIp || null, + current: envProxyIp || defaultProxyIp, + options: { + default: { name: 'nlNode', address: defaultProxyIp }, + hk: { name: 'hkNode', address: 'proxyip.cfspider.com' } + } + }; + return new Response(x27cnEnc(JSON.stringify(proxyData)), { + headers: { + 'Content-Type': 'text/plain', + 'Access-Control-Allow-Origin': '*', + 'X-Enc': 'x27cn' + } + }); + } + + if (apiPath === 'api/uuid' || apiPath === 'api/config') { + const newIpEnabled = env.NEW_IP !== 'false' && env.NEW_IP !== '0'; + const twoProxyConfig = env.TWO_PROXY || env.two_proxy || ''; + const configResponse = { + host: url.hostname, + new_ip: newIpEnabled, + version: '1.8.7', + is_default_uuid: isDefaultUUID, + two_proxy_enabled: !!twoProxyConfig + }; + if (isDefaultUUID) { + configResponse.uuid = userID; + configResponse.vless_path = twoProxyConfig + ? '/' + userID + '?two_proxy=' + encodeURIComponent(twoProxyConfig) + : '/' + userID; + } else if (twoProxyConfig) { + configResponse.two_proxy = twoProxyConfig; + } + if (twoProxyConfig) { + const parts = twoProxyConfig.split(':'); + configResponse.two_proxy_host = parts[0] || ''; + configResponse.two_proxy_port = parts[1] || ''; + } + return new Response(x27cnEnc(JSON.stringify(configResponse)), { + headers: { + 'Content-Type': 'text/plain', + 'Access-Control-Allow-Origin': '*', + 'X-Enc': 'x27cn' + } + }); + } + + if (apiPath === 'proxy' || apiPath.startsWith('proxy?')) { + const targetUrl = url.searchParams.get('url'); + const method = url.searchParams.get('method') || 'GET'; + const twoProxyParam = url.searchParams.get('two_proxy'); + + if (!targetUrl) { + return new Response(JSON.stringify({error: 'Missing url parameter'}), { + status: 400, + headers: {'Content-Type': 'application/json'} + }); + } + + try { + + const proxyHeaders = {}; + for (const [key, value] of request.headers) { + if (key.toLowerCase().startsWith('x-custom-header-')) { + const originalKey = key.substring(18); // remove 'x-custom-header-' prefix + proxyHeaders[originalKey] = value; + } + } + + let response; + + + const twoProxy = twoProxyParam || env.TWO_PROXY || env.two_proxy || ''; + + if (twoProxy) { + + const proxyParts = twoProxy.split(':'); + const proxyHost = proxyParts[0]; + const proxyPort = parseInt(proxyParts[1]) || 3128; + const proxyUser = proxyParts[2] || ''; + const proxyPass = proxyParts[3] || ''; + + + const targetParsed = new URL(targetUrl); + const targetHost = targetParsed.hostname; + const targetPort = targetParsed.port || (targetParsed.protocol === 'https:' ? 443 : 80); + const isHttps = targetParsed.protocol === 'https:'; + + + const { connect } = await import('cloudflare:sockets'); + + if (isHttps) { + + return new Response(JSON.stringify({ + error: 'HTTPS + two_proxy notSupportedViaProxyAPI。useCfspiderGetWithTwoProxy。', + hint: 'client.get(url, cf_proxies=..., uuid=..., two_proxy=...)', + reason: 'Workers /proxy API onlyHTTPTwoProxySupported' + }), { + status: 501, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + } + }); + } else { + + const socket = connect({ + hostname: proxyHost, + port: proxyPort + }); + + const writer = socket.writable.getWriter(); + const reader = socket.readable.getReader(); + + + let httpReq = `${method} ${targetUrl} HTTP/1.1\r\nHost: ${targetHost}\r\n`; + if (proxyUser && proxyPass) { + const auth = btoa(`${proxyUser}:${proxyPass}`); + httpReq += `Proxy-Authorization: Basic ${auth}\r\n`; + } + for (const [key, value] of Object.entries(proxyHeaders)) { + httpReq += `${key}: ${value}\r\n`; + } + httpReq += 'Connection: close\r\n\r\n'; + + await writer.write(new TextEncoder().encode(httpReq)); + + + let responseData = new Uint8Array(0); + while (true) { + const { value, done } = await reader.read(); + if (done) break; + const newData = new Uint8Array(responseData.length + value.length); + newData.set(responseData); + newData.set(value, responseData.length); + responseData = newData; + } + + + const responseText = new TextDecoder().decode(responseData); + const headerEnd = responseText.indexOf('\r\n\r\n'); + const headers = responseText.substring(0, headerEnd); + const body = responseData.slice(new TextEncoder().encode(responseText.substring(0, headerEnd + 4)).length); + + const statusLine = headers.split('\r\n')[0]; + const statusCode = parseInt(statusLine.split(' ')[1]) || 200; + + const responseHeaders = new Headers(); + headers.split('\r\n').slice(1).forEach(line => { + const [key, ...valueParts] = line.split(':'); + if (key && valueParts.length) { + responseHeaders.set(key.trim(), valueParts.join(':').trim()); + } + }); + responseHeaders.set('Access-Control-Allow-Origin', '*'); + responseHeaders.set('X-CF-Colo', request.cf?.colo || 'unknown'); + responseHeaders.set('X-Worker-Version', '1.8.6'); + responseHeaders.set('X-Two-Proxy', 'enabled'); + + return new Response(body, { + status: statusCode, + headers: responseHeaders + }); + } + } else { + + const proxyRequest = new Request(targetUrl, { + method: method, + headers: proxyHeaders, + body: method !== 'GET' && method !== 'HEAD' ? request.body : null + }); + + response = await fetch(proxyRequest); + + + const responseHeaders = new Headers(response.headers); + responseHeaders.set('Access-Control-Allow-Origin', '*'); + responseHeaders.set('X-CF-Colo', request.cf?.colo || 'unknown'); + responseHeaders.set('X-Worker-Version', '1.8.6'); + + return new Response(response.body, { + status: response.status, + headers: responseHeaders + }); + } + } catch (error) { + return new Response(JSON.stringify({error: error.message}), { + status: 500, + headers: {'Content-Type': 'application/json'} + }); + } + } + + + if (apiPath === 'api/config/new_ip' && request.method === 'POST') { + + const newIpEnabled = env.NEW_IP !== 'false' && env.NEW_IP !== '0'; + return new Response(JSON.stringify({ + new_ip: newIpEnabled, + message: 'setNEWIPViaDashboard' + }), { + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + } + }); + } + + if (!adminPassword) return fetch(pagesStaticUrl + '/noADMIN').then(r => { const headers = new Headers(r.headers); headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); headers.set('Pragma', 'no-cache'); headers.set('Expires', '0'); return new Response(r.body, { status: 404, statusText: r.statusText, headers }); }); + if (env.KV && typeof env.KV.get === 'function') { + const visitpath = url.pathname.slice(1).toLowerCase(); + const caseSensitivePathpath = url.pathname.slice(1); + if (caseSensitivePathpath === encryptKey && encryptKey !== 'doNotModifyDefaultKey') {//fastsub + const params = new URLSearchParams(url.search); + params.set('token', await MD5MD5(host + userID)); + return new Response('redirecting...', { status: 302, headers: { 'Location': `/sub?${params.toString()}` } }); + } else if (visitpath === 'login') {//handleloginPageAndrequest + const cookies = request.headers.get('Cookie') || ''; + const authCookie = cookies.split(';').find(c => c.trim().startsWith('auth='))?.split('=')[1]; + if (authCookie == await MD5MD5(UA + encryptKey + adminPassword)) return new Response('redirecting...', { status: 302, headers: { 'Location': '/admin' } }); + if (request.method === 'POST') { + const formData = await request.text(); + const params = new URLSearchParams(formData); + const inputPassword = params.get('password'); + if (inputPassword === adminPassword) { + + const resp = new Response(JSON.stringify({ success: true }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + resp.headers.set('Set-Cookie', `auth=${await MD5MD5(UA + encryptKey + adminPassword)}; Path=/; Max-Age=86400; HttpOnly`); + return resp; + } + } + return fetch(pagesStaticUrl + '/login'); + } else if (visitpath === 'admin' || visitpath.startsWith('admin/')) {//verifycookieafterrespadminPage + const cookies = request.headers.get('Cookie') || ''; + const authCookie = cookies.split(';').find(c => c.trim().startsWith('auth='))?.split('=')[1]; + + if (!authCookie || authCookie !== await MD5MD5(UA + encryptKey + adminPassword)) return new Response('redirecting...', { status: 302, headers: { 'Location': '/login' } }); + if (visitpath === 'admin/log.json') {// readlogContent + const readlogContent = await env.KV.get('log.json') || '[]'; + return new Response(readlogContent, { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } else if (caseSensitivePathpath === 'admin/getCloudflareUsage') {// queryrequestamt + try { + const Usage_JSON = await getCloudflareUsage(url.searchParams.get('Email'), url.searchParams.get('GlobalAPIKey'), url.searchParams.get('AccountID'), url.searchParams.get('APIToken')); + return new Response(JSON.stringify(Usage_JSON, null, 2), { status: 200, headers: { 'Content-Type': 'application/json' } }); + } catch (err) { + const errorResponse = { msg: 'queryRequestFailed,reason:' + err.message, error: err.message }; + return new Response(JSON.stringify(errorResponse, null, 2), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else if (caseSensitivePathpath === 'admin/getADDAPI') {// verifyoptimalAPI + if (url.searchParams.get('url')) { + const pendingOptimalURL = url.searchParams.get('url'); + try { + new URL(pendingOptimalURL); + const requestOptimalAPIcontent = await requestOptimalAPI([pendingOptimalURL], url.searchParams.get('port') || '443'); + const optimalAPIIP = requestOptimalAPIcontent[0].length > 0 ? requestOptimalAPIcontent[0] : requestOptimalAPIcontent[1]; + return new Response(JSON.stringify({ success: true, data: optimalAPIIP }, null, 2), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (err) { + const errorResponse = { msg: 'verifyAPIFailed,reason:' + err.message, error: err.message }; + return new Response(JSON.stringify(errorResponse, null, 2), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } + return new Response(JSON.stringify({ success: false, data: [] }, null, 2), { status: 403, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } else if (visitpath === 'admin/check') {// SOCKS5proxycheck + let checkProxyResponse; + if (url.searchParams.has('socks5')) { + checkProxyResponse = await SOCKS5availabilityCheck('socks5', url.searchParams.get('socks5')); + } else if (url.searchParams.has('http')) { + checkProxyResponse = await SOCKS5availabilityCheck('http', url.searchParams.get('http')); + } else { + return new Response(JSON.stringify({ error: 'missingProxyParam' }), { status: 400, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + return new Response(JSON.stringify(checkProxyResponse, null, 2), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + + config_JSON = await readconfig_JSON(env, host, userID, env.PATH); + + if (visitpath === 'admin/init') {// resetCfgtoDefault + try { + config_JSON = await readconfig_JSON(env, host, userID, env.PATH, true); + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Init_Config', config_JSON)); + config_JSON.init = 'configResetToDefault'; + return new Response(JSON.stringify(config_JSON, null, 2), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (err) { + const errorResponse = { msg: 'configResetFailed,reason:' + err.message, error: err.message }; + return new Response(JSON.stringify(errorResponse, null, 2), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else if (request.method === 'POST') {// handle KV op(POST request) + if (visitpath === 'admin/config.json') { // saveconfig.jsonconfig + try { + const newConfig = await request.json(); + + if (!newConfig.UUID || !newConfig.HOST) return new Response(JSON.stringify({ error: 'incompleteConfig' }), { status: 400, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + + + await env.KV.put('config.json', JSON.stringify(newConfig, null, 2)); + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Save_Config', config_JSON)); + return new Response(JSON.stringify({ success: true, message: 'configSaved' }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (error) { + console.error('saveConfigFailed:', error); + return new Response(JSON.stringify({ error: 'saveConfigFailed: ' + error.message }), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else if (visitpath === 'admin/cf.json') { // savecf.jsonconfig + try { + const newConfig = await request.json(); + const CF_JSON = { Email: null, GlobalAPIKey: null, AccountID: null, APIToken: null, UsageAPI: null }; + if (!newConfig.init || newConfig.init !== true) { + if (newConfig.Email && newConfig.GlobalAPIKey) { + CF_JSON.Email = newConfig.Email; + CF_JSON.GlobalAPIKey = newConfig.GlobalAPIKey; + } else if (newConfig.AccountID && newConfig.APIToken) { + CF_JSON.AccountID = newConfig.AccountID; + CF_JSON.APIToken = newConfig.APIToken; + } else if (newConfig.UsageAPI) { + CF_JSON.UsageAPI = newConfig.UsageAPI; + } else { + return new Response(JSON.stringify({ error: 'incompleteConfig' }), { status: 400, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } + + + await env.KV.put('cf.json', JSON.stringify(CF_JSON, null, 2)); + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Save_Config', config_JSON)); + return new Response(JSON.stringify({ success: true, message: 'configSaved' }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (error) { + console.error('saveConfigFailed:', error); + return new Response(JSON.stringify({ error: 'saveConfigFailed: ' + error.message }), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else if (visitpath === 'admin/tg.json') { // savetg.jsonconfig + try { + const newConfig = await request.json(); + if (newConfig.init && newConfig.init === true) { + const TG_JSON = { BotToken: null, ChatID: null }; + await env.KV.put('tg.json', JSON.stringify(TG_JSON, null, 2)); + } else { + if (!newConfig.BotToken || !newConfig.ChatID) return new Response(JSON.stringify({ error: 'incompleteConfig' }), { status: 400, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + await env.KV.put('tg.json', JSON.stringify(newConfig, null, 2)); + } + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Save_Config', config_JSON)); + return new Response(JSON.stringify({ success: true, message: 'configSaved' }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (error) { + console.error('saveConfigFailed:', error); + return new Response(JSON.stringify({ error: 'saveConfigFailed: ' + error.message }), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else if (caseSensitivePathpath === 'admin/ADD.txt') { // saveCustomoptimalIP + try { + const customIPs = await request.text(); + await env.KV.put('ADD.txt', customIPs);// saveTo KV + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Save_Custom_IPs', config_JSON)); + return new Response(JSON.stringify({ success: true, message: 'customIPSaved' }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (error) { + console.error('saveCustomIPfailed:', error); + return new Response(JSON.stringify({ error: 'saveCustomIPfailed: ' + error.message }), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else return new Response(JSON.stringify({ error: 'unsupportedPOSTPath' }), { status: 404, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } else if (visitpath === 'admin/config.json') {// handle admin/config.json request,returnJSON + return new Response(JSON.stringify(config_JSON, null, 2), { status: 200, headers: { 'Content-Type': 'application/json' } }); + } else if (caseSensitivePathpath === 'admin/ADD.txt') {// handle admin/ADD.txt request,returnlocaloptimalIP + let localoptimalIP = await env.KV.get('ADD.txt') || 'null'; + if (localoptimalIP == 'null') localoptimalIP = (await genrandomIP(request, config_JSON.subGenerator.localIPPool.randomCount, config_JSON.subGenerator.localIPPool.specifiedPort))[1]; + return new Response(localoptimalIP, { status: 200, headers: { 'Content-Type': 'text/plain;charset=utf-8', 'asn': request.cf.asn } }); + } else if (visitpath === 'admin/cf.json') {// CFcfgFile + return new Response(JSON.stringify(request.cf, null, 2), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Admin_Login', config_JSON)); + return fetch(pagesStaticUrl + '/admin'); + } else if (visitpath === 'logout' || uuidRegex.test(visitpath)) {//clearcookieredirectToLogin + const resp = new Response('redirecting...', { status: 302, headers: { 'Location': '/login' } }); + resp.headers.set('Set-Cookie', 'auth=; Path=/; Max-Age=0; HttpOnly'); + return resp; + } else if (visitpath === 'sub') {//handlesubrequest + const subTOKEN = await MD5MD5(host + userID); + if (url.searchParams.get('token') === subTOKEN) { + config_JSON = await readconfig_JSON(env, host, userID, env.PATH); + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Get_SUB', config_JSON)); + const ua = UA.toLowerCase(); + const expire = 4102329600;//2099-12-31 expiretime + const now = Date.now(); + const today = new Date(now); + today.setHours(0, 0, 0, 0); + const UD = Math.floor(((now - today.getTime()) / 86400000) * 24 * 1099511627776 / 2); + let pagesSum = UD, workersSum = UD, total = 24 * 1099511627776; + if (config_JSON.CF.Usage.success) { + pagesSum = config_JSON.CF.Usage.pages; + workersSum = config_JSON.CF.Usage.workers; + total = Number.isFinite(config_JSON.CF.Usage.max) ? (config_JSON.CF.Usage.max / 1000) * 1024 : 1024 * 100; + } + const responseHeaders = { + "content-type": "text/plain; charset=utf-8", + "Profile-Update-Interval": config_JSON.subGenerator.SUBUpdateTime, + "Profile-web-page-url": url.protocol + '//' + url.host + '/admin', + "Subscription-Userinfo": `upload=${pagesSum}; download=${workersSum}; total=${total}; expire=${expire}`, + "Cache-Control": "no-store", + }; + const isSubConverterRequest = url.searchParams.has('b64') || url.searchParams.has('base64') || request.headers.get('subconverter-request') || request.headers.get('subconverter-version') || ua.includes('subconverter') || ua.includes(('CF-Workers-SUB').toLowerCase()); + const subtype = isSubConverterRequest + ? 'mixed' + : url.searchParams.has('target') + ? url.searchParams.get('target') + : url.searchParams.has('clash') || ua.includes('clash') || ua.includes('meta') || ua.includes('mihomo') + ? 'clash' + : url.searchParams.has('sb') || url.searchParams.has('singbox') || ua.includes('singbox') || ua.includes('sing-box') + ? 'singbox' + : url.searchParams.has('surge') || ua.includes('surge') + ? 'surge&ver=4' + : url.searchParams.has('quanx') || ua.includes('quantumult') + ? 'quanx' + : url.searchParams.has('loon') || ua.includes('loon') + ? 'loon' + : 'mixed'; + + if (!ua.includes('mozilla')) responseHeaders["Content-Disposition"] = `attachment; filename*=utf-8''${encodeURIComponent(config_JSON.subGenerator.SUBNAME)}`; + const protocolType = (url.searchParams.has('surge') || ua.includes('surge')) ? 'tro' + 'jan' : config_JSON.protocolType; + let subContent = ''; + if (subtype === 'mixed') { + const nodepath = config_JSON.enable0RTT ? config_JSON.PATH + '?ed=2560' : config_JSON.PATH; + const tlsFragmentparam = config_JSON.tlsFragment == 'Shadowrocket' ? `&fragment=${encodeURIComponent('1,40-60,30-50,tlshello')}` : config_JSON.tlsFragment == 'Happ' ? `&fragment=${encodeURIComponent('3,1,tlshello')}` : ''; + let fullOptimalIP = [], otherNodesLINK = ''; + + if (!url.searchParams.has('sub') && config_JSON.subGenerator.local) { // localgensub + const fullOptimalList = config_JSON.subGenerator.localIPPool.randomIP ? (await genrandomIP(request, config_JSON.subGenerator.localIPPool.randomCount, config_JSON.subGenerator.localIPPool.specifiedPort))[0] : await env.KV.get('ADD.txt') ? await parseToArray(await env.KV.get('ADD.txt')) : (await genrandomIP(request, config_JSON.subGenerator.localIPPool.randomCount, config_JSON.subGenerator.localIPPool.specifiedPort))[0]; + const optimalAPI = [], optimalIP = [], otherNodes = []; + for (const element of fullOptimalList) { + if (element.toLowerCase().startsWith('https://')) optimalAPI.push(element); + else if (element.toLowerCase().includes('://')) { + if (element.includes('#')) { + const addrRemarkSplit = element.split('#'); + otherNodes.push(addrRemarkSplit[0] + '#' + encodeURIComponent(decodeURIComponent(addrRemarkSplit[1]))); + } else otherNodes.push(element); + } else optimalIP.push(element); + } + const requestOptimalAPIcontent = await requestOptimalAPI(optimalAPI); + const mergeOtherNodes = [...new Set(otherNodes.concat(requestOptimalAPIcontent[1]))]; + otherNodesLINK = mergeOtherNodes.length > 0 ? mergeOtherNodes.join('\n') + '\n' : ''; + const optimalAPIIP = requestOptimalAPIcontent[0]; + fullOptimalIP = [...new Set(optimalIP.concat(optimalAPIIP))]; + } else { // subGeneratorer + let subGeneratorerHOST = url.searchParams.get('sub') || config_JSON.subGenerator.SUB; + subGeneratorerHOST = subGeneratorerHOST && !/^https?:\/\//i.test(subGeneratorerHOST) ? `https://${subGeneratorerHOST}` : subGeneratorerHOST; + const subGeneratorerURL = `${subGeneratorerHOST}/sub?host=example.com&uuid=00000000-0000-4000-8000-000000000000`; + try { + const response = await fetch(subGeneratorerURL, { headers: { 'User-Agent': 'v2rayN/edge' + 'tunnel (https://github.com/cmliu/edge' + 'tunnel)' } }); + if (!response.ok) return new Response('subGenError:' + response.statusText, { status: response.status }); + const subGeneratorerReturnsubContent = atob(await response.text()); + const subLineList = subGeneratorerReturnsubContent.includes('\r\n') ? subGeneratorerReturnsubContent.split('\r\n') : subGeneratorerReturnsubContent.split('\n'); + for (const lineContent of subLineList) { + if (!lineContent.trim()) continue; // skipEmpty + if (lineContent.includes('00000000-0000-4000-8000-000000000000') && lineContent.includes('example.com')) { // thisIsoptimalIPline,extract domain:port#remark + const addrMatch = lineContent.match(/:\/\/[^@]+@([^?]+)/); + if (addrMatch) { + let addrPort = addrMatch[1], remark = ''; // domain:port or IP:port + const remarkMatch = lineContent.match(/#(.+)$/); + if (remarkMatch) remark = '#' + decodeURIComponent(remarkMatch[1]); + fullOptimalIP.push(addrPort + remark); + } + } else otherNodesLINK += lineContent + '\n'; + } + } catch (error) { + return new Response('subGenError:' + error.message, { status: 403 }); + } + } + const ECHLINKparam = config_JSON.ECH ? `&ech=${encodeURIComponent('cloudflare-ech.com+' + ECH_DOH)}` : ''; + subContent = otherNodesLINK + fullOptimalIP.map(origAddr => { + + const regex = /^(\[[\da-fA-F:]+\]|[\d.]+|[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*)(?::(\d+))?(?:#(.+))?$/; + const match = origAddr.match(regex); + + let nodeAddr, nodePort = "443", nodeRemark; + + if (match) { + nodeAddr = match[1]; // IPaddrordomain(mayHaveBrackets) + nodePort = match[2] || "443"; // port,def443 + nodeRemark = match[3] || nodeAddr; // remark,defToaddritself + } else { + + console.warn(`[subContent] invalidIPIgnored: ${origAddr}`); + return null; + } + + return `${protocolType}://00000000-0000-4000-8000-000000000000@${nodeAddr}:${nodePort}?security=tls&type=${config_JSON.transport + ECHLINKparam}&host=example.com&fp=${config_JSON.Fingerprint}&sni=example.com&path=${encodeURIComponent(config_JSON.randomPath ? randomPath() + nodepath : nodepath) + tlsFragmentparam}&encryption=none${config_JSON.skipCertVerify ? '&insecure=1&allowInsecure=1' : ''}#${encodeURIComponent(nodeRemark)}`; + }).filter(item => item !== null).join('\n'); + } else { // subConvert + const subConvertURL = `${config_JSON.subConverterConfig.SUBAPI}/sub?target=${subtype}&url=${encodeURIComponent(url.protocol + '//' + url.host + '/sub?target=mixed&token=' + subTOKEN + (url.searchParams.has('sub') && url.searchParams.get('sub') != '' ? `&sub=${url.searchParams.get('sub')}` : ''))}&config=${encodeURIComponent(config_JSON.subConverterConfig.SUBCONFIG)}&emoji=${config_JSON.subConverterConfig.SUBEMOJI}&scv=${config_JSON.skipCertVerify}`; + try { + const response = await fetch(subConvertURL, { headers: { 'User-Agent': 'Subconverter for ' + subtype + ' edge' + 'tunnel(https://github.com/cmliu/edge' + 'tunnel)' } }); + if (response.ok) { + subContent = await response.text(); + if (url.searchParams.has('surge') || ua.includes('surge')) subContent = SurgesubConfigHotfix(subContent, url.protocol + '//' + url.host + '/sub?token=' + subTOKEN + '&surge', config_JSON); + } else return new Response('subConverterError:' + response.statusText, { status: response.status }); + } catch (error) { + return new Response('subConverterError:' + error.message, { status: 403 }); + } + } + + if (!ua.includes('subconverter')) subContent = await batchReplacedomain(subContent.replace(/00000000-0000-4000-8000-000000000000/g, config_JSON.UUID), config_JSON.HOSTS) + + if (subtype === 'mixed' && (!ua.includes('mozilla') || url.searchParams.has('b64') || url.searchParams.has('base64'))) subContent = btoa(subContent); + + if (subtype === 'singbox') { + subContent = SingboxsubConfigHotfix(subContent, config_JSON.UUID, config_JSON.Fingerprint, config_JSON.ECH ? await getECH(host) : null); + responseHeaders["content-type"] = 'application/json; charset=utf-8'; + } else if (subtype === 'clash') { + subContent = ClashsubConfigHotfix(subContent, config_JSON.UUID, config_JSON.ECH, config_JSON.HOSTS); + responseHeaders["content-type"] = 'application/x-yaml; charset=utf-8'; + } + return new Response(subContent, { status: 200, headers: responseHeaders }); + } + } else if (visitpath === 'locations') {//reverseProxylocationslist + const cookies = request.headers.get('Cookie') || ''; + const authCookie = cookies.split(';').find(c => c.trim().startsWith('auth='))?.split('=')[1]; + if (authCookie && authCookie == await MD5MD5(UA + encryptKey + adminPassword)) return fetch(new Request('https://speed.cloudflare.com/locations', { headers: { 'Referer': 'https://speed.cloudflare.com/' } })); + } else if (visitpath === 'robots.txt') return new Response('User-agent: *\nDisallow: /', { status: 200, headers: { 'Content-Type': 'text/plain; charset=UTF-8' } }); + } else if (!envUUID) return fetch(pagesStaticUrl + '/noKV').then(r => { const headers = new Headers(r.headers); headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); headers.set('Pragma', 'no-cache'); headers.set('Expires', '0'); return new Response(r.body, { status: 404, statusText: r.statusText, headers }); }); + } else if (adminPassword) {// wsproxy + await reverseProxyparamget(request); + + const urlPath = new URL(request.url).pathname; + let twoProxyFromPath = ''; + if (urlPath.includes('two_proxy=')) { + const match = urlPath.match(/two_proxy=([^&]+)/); + if (match) { + twoProxyFromPath = decodeURIComponent(decodeURIComponent(match[1])); + } + } + + const twoProxyConfig = twoProxyFromPath || env.TWO_PROXY || env.two_proxy || ''; + return await handleWSrequest(request, userID, twoProxyConfig); + } + + let fakePageURL = env.URL || 'nginx'; + if (fakePageURL && fakePageURL !== 'nginx' && fakePageURL !== '1101') { + fakePageURL = fakePageURL.trim().replace(/\/$/, ''); + if (!fakePageURL.match(/^https?:\/\//i)) fakePageURL = 'https://' + fakePageURL; + if (fakePageURL.toLowerCase().startsWith('http://')) fakePageURL = 'https://' + fakePageURL.substring(7); + try { const u = new URL(fakePageURL); fakePageURL = u.protocol + '//' + u.host; } catch (e) { fakePageURL = 'nginx'; } + } + if (fakePageURL === '1101') return new Response(await html1101(url.host, clientIP), { status: 200, headers: { 'Content-Type': 'text/html; charset=UTF-8' } }); + try { + const reverseProxyURL = new URL(fakePageURL), newHeaders = new Headers(request.headers); + newHeaders.set('Host', reverseProxyURL.host); + newHeaders.set('Referer', reverseProxyURL.origin); + newHeaders.set('Origin', reverseProxyURL.origin); + if (!newHeaders.has('User-Agent') && UA && UA !== 'null') newHeaders.set('User-Agent', UA); + const reverseProxyresp = await fetch(reverseProxyURL.origin + url.pathname + url.search, { method: request.method, headers: newHeaders, body: request.body, cf: request.cf }); + const contenttype = reverseProxyresp.headers.get('content-type') || ''; + + if (/text|javascript|json|xml/.test(contenttype)) { + const respContent = (await reverseProxyresp.text()).replaceAll(reverseProxyURL.host, url.host); + return new Response(respContent, { status: reverseProxyresp.status, headers: { ...Object.fromEntries(reverseProxyresp.headers), 'Cache-Control': 'no-store' } }); + } + return reverseProxyresp; + } catch (error) { } + return new Response(await nginx(), { status: 200, headers: { 'Content-Type': 'text/html; charset=UTF-8' } }); + } +}; + +async function handleWSrequest(request, yourUUID, twoProxy = '') { + const wssPair = new WebSocketPair(); + const [clientSock, serverSock] = Object.values(wssPair); + serverSock.accept(); + let remoteConnWrapper = { socket: null }; + let isDnsQuery = false; + const earlyData = request.headers.get('sec-websocket-protocol') || ''; + const readable = makeReadableStr(serverSock, earlyData); + let checkIfTrojan = null; + + + let twoProxyParsed = null; + if (twoProxy) { + const parts = twoProxy.split(':'); + if (parts.length >= 2) { + twoProxyParsed = { + hostname: parts[0], + port: parseInt(parts[1], 10), + username: parts[2] || '', + password: parts[3] || '' + }; + console.log(`[twoProxy] enabled: ${twoProxyParsed.hostname}:${twoProxyParsed.port}`); + } + } + + readable.pipeTo(new WritableStream({ + async write(chunk) { + if (isDnsQuery) return await forwardataudp(chunk, serverSock, null); + if (remoteConnWrapper.socket) { + const writer = remoteConnWrapper.socket.writable.getWriter(); + await writer.write(chunk); + writer.releaseLock(); + return; + } + + if (checkIfTrojan === null) { + const bytes = new Uint8Array(chunk); + checkIfTrojan = bytes.byteLength >= 58 && bytes[56] === 0x0d && bytes[57] === 0x0a; + } + + if (remoteConnWrapper.socket) { + const writer = remoteConnWrapper.socket.writable.getWriter(); + await writer.write(chunk); + writer.releaseLock(); + return; + } + + if (checkIfTrojan) { + const { port, hostname, rawClientData } = parseTrojanReq(chunk, yourUUID); + if (isSpeedTestSite(hostname)) throw new Error('Speedtest site is blocked'); + await forwardataTCP(hostname, port, rawClientData, serverSock, null, remoteConnWrapper, yourUUID, twoProxyParsed); + } else { + const { port, hostname, rawIndex, version, isUDP } = parseVlessReq(chunk, yourUUID); + if (isSpeedTestSite(hostname)) throw new Error('Speedtest site is blocked'); + if (isUDP) { + if (port === 53) isDnsQuery = true; + else throw new Error('UDP is not supported'); + } + const respHeader = new Uint8Array([version[0], 0]); + const rawData = chunk.slice(rawIndex); + if (isDnsQuery) return forwardataudp(rawData, serverSock, respHeader); + await forwardataTCP(hostname, port, rawData, serverSock, respHeader, remoteConnWrapper, yourUUID, twoProxyParsed); + } + }, + })).catch((err) => { + // console.error('Readable pipe error:', err); + }); + + return new Response(null, { status: 101, webSocket: clientSock }); +} + +function parseTrojanReq(buffer, passwordPlainText) { + const sha224Password = sha224(passwordPlainText); + if (buffer.byteLength < 56) return { hasError: true, message: "invalid data" }; + let crLfIndex = 56; + if (new Uint8Array(buffer.slice(56, 57))[0] !== 0x0d || new Uint8Array(buffer.slice(57, 58))[0] !== 0x0a) return { hasError: true, message: "invalid header format" }; + const password = new TextDecoder().decode(buffer.slice(0, crLfIndex)); + if (password !== sha224Password) return { hasError: true, message: "invalid password" }; + + const socks5DataBuffer = buffer.slice(crLfIndex + 2); + if (socks5DataBuffer.byteLength < 6) return { hasError: true, message: "invalid S5 request data" }; + + const view = new DataView(socks5DataBuffer); + const cmd = view.getUint8(0); + if (cmd !== 1) return { hasError: true, message: "unsupported command, only TCP is allowed" }; + + const atype = view.getUint8(1); + let addressLength = 0; + let addressIndex = 2; + let address = ""; + switch (atype) { + case 1: // IPv4 + addressLength = 4; + address = new Uint8Array(socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)).join("."); + break; + case 3: // Domain + addressLength = new Uint8Array(socks5DataBuffer.slice(addressIndex, addressIndex + 1))[0]; + addressIndex += 1; + address = new TextDecoder().decode(socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)); + break; + case 4: // IPv6 + addressLength = 16; + const dataView = new DataView(socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)); + const ipv6 = []; + for (let i = 0; i < 8; i++) { + ipv6.push(dataView.getUint16(i * 2).toString(16)); + } + address = ipv6.join(":"); + break; + default: + return { hasError: true, message: `invalid addressType is ${atype}` }; + } + + if (!address) { + return { hasError: true, message: `address is empty, addressType is ${atype}` }; + } + + const portIndex = addressIndex + addressLength; + const portBuffer = socks5DataBuffer.slice(portIndex, portIndex + 2); + const portRemote = new DataView(portBuffer).getUint16(0); + + return { + hasError: false, + addressType: atype, + port: portRemote, + hostname: address, + rawClientData: socks5DataBuffer.slice(portIndex + 4) + }; +} + +function parseVlessReq(chunk, token) { + if (chunk.byteLength < 24) return { hasError: true, message: 'Invalid data' }; + const version = new Uint8Array(chunk.slice(0, 1)); + if (formatIdentifier(new Uint8Array(chunk.slice(1, 17))) !== token) return { hasError: true, message: 'Invalid uuid' }; + const optLen = new Uint8Array(chunk.slice(17, 18))[0]; + const cmd = new Uint8Array(chunk.slice(18 + optLen, 19 + optLen))[0]; + let isUDP = false; + if (cmd === 1) { } else if (cmd === 2) { isUDP = true; } else { return { hasError: true, message: 'Invalid command' }; } + const portIdx = 19 + optLen; + const port = new DataView(chunk.slice(portIdx, portIdx + 2)).getUint16(0); + let addrIdx = portIdx + 2, addrLen = 0, addrValIdx = addrIdx + 1, hostname = ''; + const addressType = new Uint8Array(chunk.slice(addrIdx, addrValIdx))[0]; + switch (addressType) { + case 1: + addrLen = 4; + hostname = new Uint8Array(chunk.slice(addrValIdx, addrValIdx + addrLen)).join('.'); + break; + case 2: + addrLen = new Uint8Array(chunk.slice(addrValIdx, addrValIdx + 1))[0]; + addrValIdx += 1; + hostname = new TextDecoder().decode(chunk.slice(addrValIdx, addrValIdx + addrLen)); + break; + case 3: + addrLen = 16; + const ipv6 = []; + const ipv6View = new DataView(chunk.slice(addrValIdx, addrValIdx + addrLen)); + for (let i = 0; i < 8; i++) ipv6.push(ipv6View.getUint16(i * 2).toString(16)); + hostname = ipv6.join(':'); + break; + default: + return { hasError: true, message: `Invalid address type: ${addressType}` }; + } + if (!hostname) return { hasError: true, message: `Invalid address: ${addressType}` }; + return { hasError: false, addressType, port, hostname, isUDP, rawIndex: addrValIdx + addrLen, version }; +} +async function forwardataTCP(host, portNum, rawData, ws, respHeader, remoteConnWrapper, yourUUID, twoProxy = null) { + console.log(`[TCPforward] target: ${host}:${portNum} | proxyIP: ${proxyIP} | twoProxy: ${twoProxy ? twoProxy.hostname + ':' + twoProxy.port : 'no'} | proxyType: ${enableSOCKS5Proxy || 'proxyip'}`); + + async function connectDirect(address, port, data, allreverseProxyarr = null, reverseProxyfallback = true) { + let remoteSock; + if (allreverseProxyarr && allreverseProxyarr.length > 0) { + for (let i = 0; i < allreverseProxyarr.length; i++) { + const reverseProxyarrIndex = (cachedProxyIndex + i) % allreverseProxyarr.length; + const [reverseProxyaddr, reverseProxyport] = allreverseProxyarr[reverseProxyarrIndex]; + try { + console.log(`[proxyConn] connectingTo: ${reverseProxyaddr}:${reverseProxyport} (idx: ${reverseProxyarrIndex})`); + remoteSock = connect({ hostname: reverseProxyaddr, port: reverseProxyport }); + + await Promise.race([ + remoteSock.opened, + new Promise((_, reject) => setTimeout(() => reject(new Error('connTimeout')), 1000)) + ]); + const testWriter = remoteSock.writable.getWriter(); + await testWriter.write(data); + testWriter.releaseLock(); + console.log(`[proxyConn] connectedTo: ${reverseProxyaddr}:${reverseProxyport}`); + cachedProxyIndex = reverseProxyarrIndex; + return remoteSock; + } catch (err) { + console.log(`[proxyConn] connFailed: ${reverseProxyaddr}:${reverseProxyport}, err: ${err.message}`); + try { remoteSock?.close?.(); } catch (e) { } + continue; + } + } + } + + if (reverseProxyfallback) { + remoteSock = connect({ hostname: address, port: port }); + const writer = remoteSock.writable.getWriter(); + await writer.write(data); + writer.releaseLock(); + return remoteSock; + } else { + closeSocketQuietly(ws); + throw new Error('[proxyConn] allProxyFailed,andNotenableProxyFallback,connTerminated。'); + } + } + + async function connecttoPry() { + let newSocket; + if (enableSOCKS5Proxy === 'socks5') { + console.log(`[SOCKS5proxy] proxyTo: ${host}:${portNum}`); + newSocket = await socks5Connect(host, portNum, rawData); + } else if (enableSOCKS5Proxy === 'http' || enableSOCKS5Proxy === 'https') { + console.log(`[HTTPproxy] proxyTo: ${host}:${portNum}`); + newSocket = await httpConnect(host, portNum, rawData); + } else { + console.log(`[proxyConn] proxyTo: ${host}:${portNum}`); + const allreverseProxyarr = await parseAddrPort(proxyIP, host, yourUUID); + newSocket = await connectDirect(atob('UFJPWFlJUC50cDEuMDkwMjI3Lnh5eg=='), 1, rawData, allreverseProxyarr, enableProxyFallback); + } + remoteConnWrapper.socket = newSocket; + newSocket.closed.catch(() => { }).finally(() => closeSocketQuietly(ws)); + connectStreams(newSocket, ws, respHeader, null); + } + + + async function connectViaTwoProxy() { + if (!twoProxy) throw new Error('twoProxyNotConfigured'); + console.log(`[twoProxy] via ${twoProxy.hostname}:${twoProxy.port} connTo ${host}:${portNum}`); + + const socket = connect({ hostname: twoProxy.hostname, port: twoProxy.port }); + const writer = socket.writable.getWriter(); + const reader = socket.readable.getReader(); + + try { + + const auth = twoProxy.username && twoProxy.password + ? `Proxy-Authorization: Basic ${btoa(`${twoProxy.username}:${twoProxy.password}`)}\r\n` + : ''; + const connectRequest = `CONNECT ${host}:${portNum} HTTP/1.1\r\nHost: ${host}:${portNum}\r\n${auth}User-Agent: Mozilla/5.0\r\nConnection: keep-alive\r\n\r\n`; + + await writer.write(new TextEncoder().encode(connectRequest)); + + + let responseBuffer = new Uint8Array(0); + let headerEndIndex = -1; + let bytesRead = 0; + + while (headerEndIndex === -1 && bytesRead < 8192) { + const { done, value } = await reader.read(); + if (done) throw new Error('proxyClosed'); + responseBuffer = new Uint8Array([...responseBuffer, ...value]); + bytesRead = responseBuffer.length; + + + for (let i = 0; i < responseBuffer.length - 3; i++) { + if (responseBuffer[i] === 0x0d && responseBuffer[i + 1] === 0x0a && + responseBuffer[i + 2] === 0x0d && responseBuffer[i + 3] === 0x0a) { + headerEndIndex = i + 4; + break; + } + } + } + + if (headerEndIndex === -1) throw new Error('invalidProxyResp'); + + const responseText = new TextDecoder().decode(responseBuffer.slice(0, headerEndIndex)); + const statusMatch = responseText.match(/HTTP\/\d\.\d\s+(\d+)/); + const statusCode = statusMatch ? parseInt(statusMatch[1]) : 0; + + if (statusCode < 200 || statusCode >= 300) { + throw new Error(`proxyConnFailed: HTTP ${statusCode}`); + } + + console.log(`[twoProxy] tunnelEstablished: ${host}:${portNum}`); + + + await writer.write(rawData); + writer.releaseLock(); + reader.releaseLock(); + + return socket; + } catch (error) { + try { writer.releaseLock(); } catch (e) { } + try { reader.releaseLock(); } catch (e) { } + try { socket.close(); } catch (e) { } + throw error; + } + } + + const verifysocks5Whitelist = (addr) => socks5Whitelist.some(p => new RegExp(`^${p.replace(/\*/g, '.*')}$`, 'i').test(addr)); + + + if (twoProxy) { + console.log(`[TCPforward] usingTwoProxy`); + try { + const proxySocket = await connectViaTwoProxy(); + remoteConnWrapper.socket = proxySocket; + proxySocket.closed.catch(() => { }).finally(() => closeSocketQuietly(ws)); + connectStreams(proxySocket, ws, respHeader, null); + } catch (err) { + console.log(`[twoProxy] connFailed: ${err.message}, fallbackToDefault`); + + try { + await connecttoPry(); + } catch (err2) { + throw err2; + } + } + } else if (enableSOCKS5Proxy && (enableGlobalSOCKS5 || verifysocks5Whitelist(host))) { + console.log(`[TCPforward] enableGlobalProxy`); + try { + await connecttoPry(); + } catch (err) { + throw err; + } + } else { + try { + console.log(`[TCPforward] directConnTo: ${host}:${portNum}`); + const initialSocket = await connectDirect(host, portNum, rawData); + remoteConnWrapper.socket = initialSocket; + connectStreams(initialSocket, ws, respHeader, connecttoPry); + } catch (err) { + await connecttoPry(); + } + } +} + +async function forwardataudp(udpChunk, webSocket, respHeader) { + try { + const tcpSocket = connect({ hostname: '8.8.4.4', port: 53 }); + let vlessHeader = respHeader; + const writer = tcpSocket.writable.getWriter(); + await writer.write(udpChunk); + writer.releaseLock(); + await tcpSocket.readable.pipeTo(new WritableStream({ + async write(chunk) { + if (webSocket.readyState === WebSocket.OPEN) { + if (vlessHeader) { + const response = new Uint8Array(vlessHeader.length + chunk.byteLength); + response.set(vlessHeader, 0); + response.set(chunk, vlessHeader.length); + webSocket.send(response.buffer); + vlessHeader = null; + } else { + webSocket.send(chunk); + } + } + }, + })); + } catch (error) { + // console.error('UDP forward error:', error); + } +} + +function closeSocketQuietly(socket) { + try { + if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CLOSING) { + socket.close(); + } + } catch (error) { } +} + +function formatIdentifier(arr, offset = 0) { + const hex = [...arr.slice(offset, offset + 16)].map(b => b.toString(16).padStart(2, '0')).join(''); + return `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20)}`; +} +async function connectStreams(remoteSocket, webSocket, headerData, retryFunc) { + let header = headerData, hasData = false; + await remoteSocket.readable.pipeTo( + new WritableStream({ + async write(chunk, controller) { + hasData = true; + if (webSocket.readyState !== WebSocket.OPEN) controller.error('ws.readyState is not open'); + if (header) { + const response = new Uint8Array(header.length + chunk.byteLength); + response.set(header, 0); + response.set(chunk, header.length); + webSocket.send(response.buffer); + header = null; + } else { + webSocket.send(chunk); + } + }, + abort() { }, + }) + ).catch((err) => { + closeSocketQuietly(webSocket); + }); + if (!hasData && retryFunc) { + await retryFunc(); + } +} + +function makeReadableStr(socket, earlyDataHeader) { + let cancelled = false; + return new ReadableStream({ + start(controller) { + socket.addEventListener('message', (event) => { + if (!cancelled) controller.enqueue(event.data); + }); + socket.addEventListener('close', () => { + if (!cancelled) { + closeSocketQuietly(socket); + controller.close(); + } + }); + socket.addEventListener('error', (err) => controller.error(err)); + const { earlyData, error } = base64ToArray(earlyDataHeader); + if (error) controller.error(error); + else if (earlyData) controller.enqueue(earlyData); + }, + cancel() { + cancelled = true; + closeSocketQuietly(socket); + } + }); +} + +function isSpeedTestSite(hostname) { + const speedTestDomains = [atob('c3BlZWQuY2xvdWRmbGFyZS5jb20=')]; + if (speedTestDomains.includes(hostname)) { + return true; + } + + for (const domain of speedTestDomains) { + if (hostname.endsWith('.' + domain) || hostname === domain) { + return true; + } + } + return false; +} + +function base64ToArray(b64Str) { + if (!b64Str) return { error: null }; + try { + const binaryString = atob(b64Str.replace(/-/g, '+').replace(/_/g, '/')); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return { earlyData: bytes.buffer, error: null }; + } catch (error) { + return { error }; + } +} + +async function socks5Connect(targetHost, targetPort, initialData) { + const { username, password, hostname, port } = parsedSocks5Address; + const socket = connect({ hostname, port }), writer = socket.writable.getWriter(), reader = socket.readable.getReader(); + try { + const authMethods = username && password ? new Uint8Array([0x05, 0x02, 0x00, 0x02]) : new Uint8Array([0x05, 0x01, 0x00]); + await writer.write(authMethods); + let response = await reader.read(); + if (response.done || response.value.byteLength < 2) throw new Error('S5 method selection failed'); + + const selectedMethod = new Uint8Array(response.value)[1]; + if (selectedMethod === 0x02) { + if (!username || !password) throw new Error('S5 requires authentication'); + const userBytes = new TextEncoder().encode(username), passBytes = new TextEncoder().encode(password); + const authPacket = new Uint8Array([0x01, userBytes.length, ...userBytes, passBytes.length, ...passBytes]); + await writer.write(authPacket); + response = await reader.read(); + if (response.done || new Uint8Array(response.value)[1] !== 0x00) throw new Error('S5 authentication failed'); + } else if (selectedMethod !== 0x00) throw new Error(`S5 unsupported auth method: ${selectedMethod}`); + + const hostBytes = new TextEncoder().encode(targetHost); + const connectPacket = new Uint8Array([0x05, 0x01, 0x00, 0x03, hostBytes.length, ...hostBytes, targetPort >> 8, targetPort & 0xff]); + await writer.write(connectPacket); + response = await reader.read(); + if (response.done || new Uint8Array(response.value)[1] !== 0x00) throw new Error('S5 connection failed'); + + await writer.write(initialData); + writer.releaseLock(); reader.releaseLock(); + return socket; + } catch (error) { + try { writer.releaseLock(); } catch (e) { } + try { reader.releaseLock(); } catch (e) { } + try { socket.close(); } catch (e) { } + throw error; + } +} + +async function httpConnect(targetHost, targetPort, initialData) { + const { username, password, hostname, port } = parsedSocks5Address; + const socket = connect({ hostname, port }), writer = socket.writable.getWriter(), reader = socket.readable.getReader(); + try { + const auth = username && password ? `Proxy-Authorization: Basic ${btoa(`${username}:${password}`)}\r\n` : ''; + const request = `CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\nHost: ${targetHost}:${targetPort}\r\n${auth}User-Agent: Mozilla/5.0\r\nConnection: keep-alive\r\n\r\n`; + await writer.write(new TextEncoder().encode(request)); + + let responseBuffer = new Uint8Array(0), headerEndIndex = -1, bytesRead = 0; + while (headerEndIndex === -1 && bytesRead < 8192) { + const { done, value } = await reader.read(); + if (done) throw new Error('Connection closed before receiving HTTP response'); + responseBuffer = new Uint8Array([...responseBuffer, ...value]); + bytesRead = responseBuffer.length; + const crlfcrlf = responseBuffer.findIndex((_, i) => i < responseBuffer.length - 3 && responseBuffer[i] === 0x0d && responseBuffer[i + 1] === 0x0a && responseBuffer[i + 2] === 0x0d && responseBuffer[i + 3] === 0x0a); + if (crlfcrlf !== -1) headerEndIndex = crlfcrlf + 4; + } + + if (headerEndIndex === -1) throw new Error('Invalid HTTP response'); + const statusCode = parseInt(new TextDecoder().decode(responseBuffer.slice(0, headerEndIndex)).split('\r\n')[0].match(/HTTP\/\d\.\d\s+(\d+)/)[1]); + if (statusCode < 200 || statusCode >= 300) throw new Error(`Connection failed: HTTP ${statusCode}`); + + await writer.write(initialData); + writer.releaseLock(); reader.releaseLock(); + return socket; + } catch (error) { + try { writer.releaseLock(); } catch (e) { } + try { reader.releaseLock(); } catch (e) { } + try { socket.close(); } catch (e) { } + throw error; + } +} + +function ClashsubConfigHotfix(Clash_rawsubContent, uuid = null, ECHenable = false, HOSTS = []) { + let clash_yaml = Clash_rawsubContent.replace(/mode:\s*Rule\b/g, 'mode: rule'); + + + const baseDnsBlock = `dns: + enable: true + default-nameserver: + - 223.5.5.5 + - 119.29.29.29 + - 114.114.114.114 + use-hosts: true + nameserver: + - https://sm2.doh.pub/dns-query + - https://dns.alidns.com/dns-query + fallback: + - 8.8.4.4 + - 101.101.101.101 + - 208.67.220.220 + fallback-filter: + geoip: true + domain: [+.google.com, +.facebook.com, +.youtube.com] + ipcidr: + - 240.0.0.0/4 + - 0.0.0.0/32 + geoip-code: CN +`; + + + const hasDns = /^dns:\s*(?:\n|$)/m.test(clash_yaml); + + + if (!hasDns) { + clash_yaml = baseDnsBlock + clash_yaml; + } + + + if (ECHenable && HOSTS.length > 0) { + + const hostsEntries = HOSTS.map(host => ` "${host}":\n - tls://8.8.8.8\n - https://doh.cmliussss.com/CMLiussss\n - ${ECH_DOH}`).join('\n'); + + + const hasNameserverPolicy = /^\s{2}nameserver-policy:\s*(?:\n|$)/m.test(clash_yaml); + + if (hasNameserverPolicy) { + + clash_yaml = clash_yaml.replace( + /^(\s{2}nameserver-policy:\s*\n)/m, + `$1${hostsEntries}\n` + ); + } else { + + const lines = clash_yaml.split('\n'); + let dnsBlockEndIndex = -1; + let inDnsBlock = false; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (/^dns:\s*$/.test(line)) { + inDnsBlock = true; + continue; + } + if (inDnsBlock) { + + if (/^[a-zA-Z]/.test(line)) { + dnsBlockEndIndex = i; + break; + } + } + } + + + const nameserverPolicyBlock = ` nameserver-policy:\n${hostsEntries}`; + if (dnsBlockEndIndex !== -1) { + lines.splice(dnsBlockEndIndex, 0, nameserverPolicyBlock); + } else { + + lines.push(nameserverPolicyBlock); + } + clash_yaml = lines.join('\n'); + } + } + + + if (!uuid || !ECHenable) return clash_yaml; + + + const lines = clash_yaml.split('\n'); + const processedLines = []; + let i = 0; + + while (i < lines.length) { + const line = lines[i]; + const trimmedLine = line.trim(); + + + if (trimmedLine.startsWith('- {') && (trimmedLine.includes('uuid:') || trimmedLine.includes('password:'))) { + let fullNode = line; + let braceCount = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length; + + + while (braceCount > 0 && i + 1 < lines.length) { + i++; + fullNode += '\n' + lines[i]; + braceCount += (lines[i].match(/\{/g) || []).length - (lines[i].match(/\}/g) || []).length; + } + + + const typeMatch = fullNode.match(/type:\s*(\w+)/); + const proxyType = typeMatch ? typeMatch[1] : 'vless'; + + + let credentialField = 'uuid'; + if (proxyType === 'trojan') { + credentialField = 'password'; + } + + + const credentialPattern = new RegExp(`${credentialField}:\\s*([^,}\\n]+)`); + const credentialMatch = fullNode.match(credentialPattern); + + if (credentialMatch && credentialMatch[1].trim() === uuid.trim()) { + + fullNode = fullNode.replace(/\}(\s*)$/, `, ech-opts: {enable: true}}$1`); + } + + processedLines.push(fullNode); + i++; + } + + else if (trimmedLine.startsWith('- name:')) { + + let nodeLines = [line]; + let baseIndent = line.search(/\S/); + let topLevelIndent = baseIndent + 2; // topLevelIndent + i++; + + + while (i < lines.length) { + const nextLine = lines[i]; + const nextTrimmed = nextLine.trim(); + + + if (!nextTrimmed) { + nodeLines.push(nextLine); + i++; + break; + } + + const nextIndent = nextLine.search(/\S/); + + + if (nextIndent <= baseIndent && nextTrimmed.startsWith('- ')) { + break; + } + + + if (nextIndent < baseIndent && nextTrimmed) { + break; + } + + nodeLines.push(nextLine); + i++; + } + + + const nodeText = nodeLines.join('\n'); + const typeMatch = nodeText.match(/type:\s*(\w+)/); + const proxyType = typeMatch ? typeMatch[1] : 'vless'; + + + let credentialField = 'uuid'; + if (proxyType === 'trojan') { + credentialField = 'password'; + } + + + const credentialPattern = new RegExp(`${credentialField}:\\s*([^\\n]+)`); + const credentialMatch = nodeText.match(credentialPattern); + + if (credentialMatch && credentialMatch[1].trim() === uuid.trim()) { + + let insertIndex = -1; + + for (let j = nodeLines.length - 1; j >= 0; j--) { + + if (nodeLines[j].trim()) { + insertIndex = j; + break; + } + } + + if (insertIndex >= 0) { + const indent = ' '.repeat(topLevelIndent); + + nodeLines.splice(insertIndex + 1, 0, + `${indent}ech-opts:`, + `${indent} enable: true` + ); + } + } + + processedLines.push(...nodeLines); + } else { + processedLines.push(line); + i++; + } + } + + return processedLines.join('\n'); +} + +function SingboxsubConfigHotfix(sb_json_text, uuid = null, fingerprint = "chrome", ech_config = null) { + try { + let config = JSON.parse(sb_json_text); + + + if (Array.isArray(config.inbounds)) { + config.inbounds.forEach(inbound => { + if (inbound.type === 'tun') { + const addresses = []; + if (inbound.inet4_address) addresses.push(inbound.inet4_address); + if (inbound.inet6_address) addresses.push(inbound.inet6_address); + if (addresses.length > 0) { + inbound.address = addresses; + delete inbound.inet4_address; + delete inbound.inet6_address; + } + + const route_addresses = []; + if (Array.isArray(inbound.inet4_route_address)) route_addresses.push(...inbound.inet4_route_address); + if (Array.isArray(inbound.inet6_route_address)) route_addresses.push(...inbound.inet6_route_address); + if (route_addresses.length > 0) { + inbound.route_address = route_addresses; + delete inbound.inet4_route_address; + delete inbound.inet6_route_address; + } + + const route_exclude_addresses = []; + if (Array.isArray(inbound.inet4_route_exclude_address)) route_exclude_addresses.push(...inbound.inet4_route_exclude_address); + if (Array.isArray(inbound.inet6_route_exclude_address)) route_exclude_addresses.push(...inbound.inet6_route_exclude_address); + if (route_exclude_addresses.length > 0) { + inbound.route_exclude_address = route_exclude_addresses; + delete inbound.inet4_route_exclude_address; + delete inbound.inet6_route_exclude_address; + } + } + }); + } + + + const ruleSetsDefinitions = new Map(); + const processRules = (rules, isDns = false) => { + if (!Array.isArray(rules)) return; + rules.forEach(rule => { + if (rule.geosite) { + const geositeList = Array.isArray(rule.geosite) ? rule.geosite : [rule.geosite]; + rule.rule_set = geositeList.map(name => { + const tag = `geosite-${name}`; + if (!ruleSetsDefinitions.has(tag)) { + ruleSetsDefinitions.set(tag, { + tag: tag, + type: "remote", + format: "binary", + url: `https://gh.090227.xyz/https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-${name}.srs`, + download_detour: "DIRECT" + }); + } + return tag; + }); + delete rule.geosite; + } + if (rule.geoip) { + const geoipList = Array.isArray(rule.geoip) ? rule.geoip : [rule.geoip]; + rule.rule_set = rule.rule_set || []; + geoipList.forEach(name => { + const tag = `geoip-${name}`; + if (!ruleSetsDefinitions.has(tag)) { + ruleSetsDefinitions.set(tag, { + tag: tag, + type: "remote", + format: "binary", + url: `https://gh.090227.xyz/https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-${name}.srs`, + download_detour: "DIRECT" + }); + } + rule.rule_set.push(tag); + }); + delete rule.geoip; + } + const targetField = isDns ? 'server' : 'outbound'; + const actionValue = String(rule[targetField]).toUpperCase(); + if (actionValue === 'REJECT' || actionValue === 'BLOCK') { + rule.action = 'reject'; + rule.method = 'drop'; // forceModern + delete rule[targetField]; + } + }); + }; + + if (config.dns && config.dns.rules) processRules(config.dns.rules, true); + if (config.route && config.route.rules) processRules(config.route.rules, false); + + if (ruleSetsDefinitions.size > 0) { + if (!config.route) config.route = {}; + config.route.rule_set = Array.from(ruleSetsDefinitions.values()); + } + + + if (!config.outbounds) config.outbounds = []; + + + config.outbounds = config.outbounds.filter(o => { + if (o.tag === 'REJECT' || o.tag === 'block') { + return false; // remove,becauseChanged action: reject + } + return true; + }); + + const existingOutboundTags = new Set(config.outbounds.map(o => o.tag)); + + if (!existingOutboundTags.has('DIRECT')) { + config.outbounds.push({ "type": "direct", "tag": "DIRECT" }); + existingOutboundTags.add('DIRECT'); + } + + if (config.dns && config.dns.servers) { + const dnsServerTags = new Set(config.dns.servers.map(s => s.tag)); + if (config.dns.rules) { + config.dns.rules.forEach(rule => { + if (rule.server && !dnsServerTags.has(rule.server)) { + if (rule.server === 'dns_block' && dnsServerTags.has('block')) { + rule.server = 'block'; + } else if (rule.server.toLowerCase().includes('block') && !dnsServerTags.has(rule.server)) { + config.dns.servers.push({ "tag": rule.server, "address": "rcode://success" }); + dnsServerTags.add(rule.server); + } + } + }); + } + } + + config.outbounds.forEach(outbound => { + if (outbound.type === 'selector' || outbound.type === 'urltest') { + if (Array.isArray(outbound.outbounds)) { + + outbound.outbounds = outbound.outbounds.filter(tag => { + const upperTag = tag.toUpperCase(); + return existingOutboundTags.has(tag) && upperTag !== 'REJECT' && upperTag !== 'BLOCK'; + }); + if (outbound.outbounds.length === 0) outbound.outbounds.push("DIRECT"); + } + } + }); + + + if (uuid) { + config.outbounds.forEach(outbound => { + + if ((outbound.uuid && outbound.uuid === uuid) || (outbound.password && outbound.password === uuid)) { + + if (!outbound.tls) { + outbound.tls = { enabled: true }; + } + + + if (fingerprint) { + outbound.tls.utls = { + enabled: true, + fingerprint: fingerprint + }; + } + + + if (ech_config) { + outbound.tls.ech = { + enabled: true, + config: `-----BEGIN ECH CONFIGS-----\n${ech_config}\n-----END ECH CONFIGS-----` + }; + } + } + }); + } + + return JSON.stringify(config, null, 2); + } catch (e) { + console.error("singboxPatchFailed:", e); + return JSON.stringify(JSON.parse(sb_json_text), null, 2); + } +} + +function SurgesubConfigHotfix(content, url, config_JSON) { + const eachLine = content.includes('\r\n') ? content.split('\r\n') : content.split('\n'); + + let output = ""; + const realSurgePath = config_JSON.enable0RTT ? config_JSON.PATH + '?ed=2560' : config_JSON.PATH; + for (let x of eachLine) { + if (x.includes('= tro' + 'jan,') && !x.includes('ws=true') && !x.includes('ws-path=')) { + const host = x.split("sni=")[1].split(",")[0]; + const backup = `sni=${host}, skip-cert-verify=${config_JSON.skipCertVerify}`; + const correct = `sni=${host}, skip-cert-verify=${config_JSON.skipCertVerify}, ws=true, ws-path=${realSurgePath}, ws-headers=Host:"${host}"`; + output += x.replace(new RegExp(backup, 'g'), correct).replace("[", "").replace("]", "") + '\n'; + } else { + output += x + '\n'; + } + } + + output = `#!MANAGED-CONFIG ${url} interval=${config_JSON.subGenerator.SUBUpdateTime * 60 * 60} strict=false` + output.substring(output.indexOf('\n')); + return output; +} + +async function requestLogRecord(env, request, clientIP, requesttype = "Get_SUB", config_JSON) { + const KVcapLimit = 4;//MB + try { + const currtime = new Date(); + const logContent = { TYPE: requesttype, IP: clientIP, ASN: `AS${request.cf.asn || '0'} ${request.cf.asOrganization || 'Unknown'}`, CC: `${request.cf.country || 'N/A'} ${request.cf.city || 'N/A'}`, URL: request.url, UA: request.headers.get('User-Agent') || 'Unknown', TIME: currtime.getTime() }; + let logArr = []; + const existLog = await env.KV.get('log.json'); + if (existLog) { + try { + logArr = JSON.parse(existLog); + if (!Array.isArray(logArr)) { logArr = [logContent]; } + else if (requesttype !== "Get_SUB") { + const thirtyMinAgotimestamp = currtime.getTime() - 30 * 60 * 1000; + if (logArr.some(log => log.TYPE !== "Get_SUB" && log.IP === clientIP && log.URL === request.url && log.UA === (request.headers.get('User-Agent') || 'Unknown') && log.TIME >= thirtyMinAgotimestamp)) return; + logArr.push(logContent); + while (JSON.stringify(logArr, null, 2).length > KVcapLimit * 1024 * 1024 && logArr.length > 0) logArr.shift(); + } else { + logArr.push(logContent); + while (JSON.stringify(logArr, null, 2).length > KVcapLimit * 1024 * 1024 && logArr.length > 0) logArr.shift(); + } + if (config_JSON.TG.enable) { + try { + const TG_TXT = await env.KV.get('tg.json'); + const TG_JSON = JSON.parse(TG_TXT); + await sendMessage(TG_JSON.BotToken, TG_JSON.ChatID, logContent, config_JSON); + } catch (error) { console.error(`readtg.jsonerror: ${error.message}`) } + } + } catch (e) { logArr = [logContent]; } + } else { logArr = [logContent]; } + await env.KV.put('log.json', JSON.stringify(logArr, null, 2)); + } catch (error) { console.error(`logFailed: ${error.message}`); } +} + +async function sendMessage(BotToken, ChatID, logContent, config_JSON) { + if (!BotToken || !ChatID) return; + + try { + const requesttime = new Date(logContent.TIME).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }); + const requestURL = new URL(logContent.URL); + const msg = `#${config_JSON.subGenerator.SUBNAME} logNotify\n\n` + + `📌 type:#${logContent.TYPE}\n` + + `🌐 IP:${logContent.IP}\n` + + `📍 location:${logContent.CC}\n` + + `🏢 ASN:${logContent.ASN}\n` + + `🔗 domain:${requestURL.host}\n` + + `🔍 path:${requestURL.pathname + requestURL.search}\n` + + `🤖 UA:${logContent.UA}\n` + + `📅 time:${requesttime}\n` + + `${config_JSON.CF.Usage.success ? `📊 requestUsage:${config_JSON.CF.Usage.total}/100000 ${((config_JSON.CF.Usage.total / 100000) * 100).toFixed(2)}%\n` : ''}`; + + const url = `https://api.telegram.org/bot${BotToken}/sendMessage?chat_id=${ChatID}&parse_mode=HTML&text=${encodeURIComponent(msg)}`; + return fetch(url, { + method: 'GET', + headers: { + 'Accept': 'text/html,application/xhtml+xml,application/xml;', + 'Accept-Encoding': 'gzip, deflate, br', + 'User-Agent': logContent.UA || 'Unknown', + } + }); + } catch (error) { console.error('Error sending message:', error) } +} + +function maskSensitive(txt, prefixLen = 3, suffixLen = 2) { + if (!txt || typeof txt !== 'string') return txt; + if (txt.length <= prefixLen + suffixLen) return txt; // likeifTooShort,directReturn + + const prefix = txt.slice(0, prefixLen); + const suffix = txt.slice(-suffixLen); + const starCount = txt.length - prefixLen - suffixLen; + + return `${prefix}${'*'.repeat(starCount)}${suffix}`; +} + +async function MD5MD5(txt) { + const encoder = new TextEncoder(); + + const hash1 = await crypto.subtle.digest('MD5', encoder.encode(txt)); + const hash1Arr = Array.from(new Uint8Array(hash1)); + const hex1 = hash1Arr.map(byte => byte.toString(16).padStart(2, '0')).join(''); + + const hash2 = await crypto.subtle.digest('MD5', encoder.encode(hex1.slice(7, 27))); + const hash2Arr = Array.from(new Uint8Array(hash2)); + const hex2 = hash2Arr.map(byte => byte.toString(16).padStart(2, '0')).join(''); + + return hex2.toLowerCase(); +} + +function randomPath() { + const commonpathdir = ["about", "account", "acg", "act", "activity", "ad", "ads", "ajax", "album", "albums", "anime", "api", "app", "apps", "archive", "archives", "article", "articles", "ask", "auth", "avatar", "bbs", "bd", "blog", "blogs", "book", "books", "bt", "buy", "cart", "category", "categories", "cb", "channel", "channels", "chat", "china", "city", "class", "classify", "clip", "clips", "club", "cn", "code", "collect", "collection", "comic", "comics", "community", "company", "config", "contact", "content", "course", "courses", "cp", "data", "detail", "details", "dh", "directory", "discount", "discuss", "dl", "dload", "doc", "docs", "document", "documents", "doujin", "download", "downloads", "drama", "edu", "en", "ep", "episode", "episodes", "event", "events", "f", "faq", "favorite", "favourites", "favs", "feedback", "file", "files", "film", "films", "forum", "forums", "friend", "friends", "game", "games", "gif", "go", "go.html", "go.php", "group", "groups", "help", "home", "hot", "htm", "html", "image", "images", "img", "index", "info", "intro", "item", "items", "ja", "jp", "jump", "jump.html", "jump.php", "jumping", "knowledge", "lang", "lesson", "lessons", "lib", "library", "link", "links", "list", "live", "lives", "m", "mag", "magnet", "mall", "manhua", "map", "member", "members", "message", "messages", "mobile", "movie", "movies", "music", "my", "new", "news", "note", "novel", "novels", "online", "order", "out", "out.html", "out.php", "outbound", "p", "page", "pages", "pay", "payment", "pdf", "photo", "photos", "pic", "pics", "picture", "pictures", "play", "player", "playlist", "post", "posts", "product", "products", "program", "programs", "project", "qa", "question", "rank", "ranking", "read", "readme", "redirect", "redirect.html", "redirect.php", "reg", "register", "res", "resource", "retrieve", "sale", "search", "season", "seasons", "section", "seller", "series", "service", "services", "setting", "settings", "share", "shop", "show", "shows", "site", "soft", "sort", "source", "special", "star", "stars", "static", "stock", "store", "stream", "streaming", "streams", "student", "study", "tag", "tags", "task", "teacher", "team", "tech", "temp", "test", "thread", "tool", "tools", "topic", "topics", "torrent", "trade", "travel", "tv", "txt", "type", "u", "upload", "uploads", "url", "urls", "user", "users", "v", "version", "video", "videos", "view", "vip", "vod", "watch", "web", "wenku", "wiki", "work", "www", "zh", "zh-cn", "zh-tw", "zip"]; + const randNum = Math.floor(Math.random() * 3 + 1); + const randomPath = commonpathdir.sort(() => 0.5 - Math.random()).slice(0, randNum).join('/'); + return `/${randomPath}`; +} + +function randReplaceWildcard(h) { + if (!h?.includes('*')) return h; + const charset = 'abcdefghijklmnopqrstuvwxyz0123456789'; + return h.replace(/\*/g, () => { + let s = ''; + for (let i = 0; i < Math.floor(Math.random() * 14) + 3; i++) + s += charset[Math.floor(Math.random() * 36)]; + return s; + }); +} + +function batchReplacedomain(content, hosts, groupCnt = 2) { + const shuffledArr = [...hosts].sort(() => Math.random() - 0.5); + let count = 0, currentRandomHost = null; + return content.replace(/example\.com/g, () => { + if (count % groupCnt === 0) currentRandomHost = randReplaceWildcard(shuffledArr[Math.floor(count / groupCnt) % shuffledArr.length]); + count++; + return currentRandomHost; + }); +} + +async function getECH(host) { + try { + const res = await fetch(`https://1.1.1.1/dns-query?name=${encodeURIComponent(host)}&type=65`, { headers: { 'accept': 'application/dns-json' } }); + const data = await res.json(); + if (!data.Answer?.length) return ''; + for (let ans of data.Answer) { + if (ans.type !== 65 || !ans.data) continue; + const match = ans.data.match(/ech=([^\s]+)/); + if (match) return match[1].replace(/"/g, ''); + if (ans.data.startsWith('\\#')) { + const hex = ans.data.split(' ').slice(2).join(''); + const bytes = new Uint8Array(hex.match(/.{1,2}/g).map(b => parseInt(b, 16))); + let offset = 2; + while (offset < bytes.length && bytes[offset++] !== 0) + offset += bytes[offset - 1]; + + while (offset + 4 <= bytes.length) { + const key = (bytes[offset] << 8) | bytes[offset + 1]; + const len = (bytes[offset + 2] << 8) | bytes[offset + 3]; + offset += 4; + + if (key === 5) return btoa(String.fromCharCode(...bytes.slice(offset, offset + len))); + offset += len; + } + } + } + return ''; + } catch { + return ''; + } +} + +async function readconfig_JSON(env, hostname, userID, path, resetCfg = false) { + + const host = hostname; + const initStarttime = performance.now(); + const defCfgJSON = { + TIME: new Date().toISOString(), + HOST: host, + HOSTS: [hostname], + UUID: userID, + protocolType: "v" + "le" + "ss", + transport: "ws", + skipCertVerify: true, + enable0RTT: false, + tlsFragment: null, + randomPath: false, + ECH: false, + Fingerprint: "chrome", + subGenerator: { + local: true, // true: basedOnlocaloptimaladdr false: subGeneratorer + localIPPool: { + randomIP: true, // when randomIP istrueeffective,enablerandomIPcnt,nothenUseKVinADD.txt + randomCount: 16, + specifiedPort: -1, + }, + SUB: null, + SUBNAME: "edge" + "tunnel", + SUBUpdateTime: 3, // subupdatetime(hour) + TOKEN: await MD5MD5(hostname + userID), + }, + subConverterConfig: { + SUBAPI: "https://SUBAPI.cmliussss.net", + SUBCONFIG: "https://raw.githubusercontent.com/cmliu/ACL4SSR/refs/heads/main/Clash/config/ACL4SSR_Online_Mini_MultiMode_CF.ini", + SUBEMOJI: false, + }, + reverseProxy: { + PROXYIP: "auto", + SOCKS5: { + enable: enableSOCKS5Proxy, + globalMode: enableGlobalSOCKS5, + account: mySOCKS5Account, + whitelist: socks5Whitelist, + }, + }, + TG: { + enable: false, + BotToken: null, + ChatID: null, + }, + CF: { + Email: null, + GlobalAPIKey: null, + AccountID: null, + APIToken: null, + UsageAPI: null, + Usage: { + success: false, + pages: 0, + workers: 0, + total: 0, + max: 100000, + }, + } + }; + + try { + let configJSON = await env.KV.get('config.json'); + if (!configJSON || resetCfg == true) { + await env.KV.put('config.json', JSON.stringify(defCfgJSON, null, 2)); + config_JSON = defCfgJSON; + } else { + config_JSON = JSON.parse(configJSON); + } + } catch (error) { + console.error(`readconfig_JSONerror: ${error.message}`); + config_JSON = defCfgJSON; + } + + config_JSON.HOST = host; + if (!config_JSON.HOSTS) config_JSON.HOSTS = [hostname]; + if (env.HOST) config_JSON.HOSTS = (await parseToArray(env.HOST)).map(h => h.toLowerCase().replace(/^https?:\/\//, '').split('/')[0].split(':')[0]); + config_JSON.UUID = userID; + config_JSON.PATH = path ? (path.startsWith('/') ? path : '/' + path) : (config_JSON.reverseProxy.SOCKS5.enable ? ('/' + config_JSON.reverseProxy.SOCKS5.enable + (config_JSON.reverseProxy.SOCKS5.globalMode ? '://' : '=') + config_JSON.reverseProxy.SOCKS5.account) : (config_JSON.reverseProxy.PROXYIP === 'auto' ? '/' : `/proxyip=${config_JSON.reverseProxy.PROXYIP}`)); + const tlsFragmentparam = config_JSON.tlsFragment == 'Shadowrocket' ? `&fragment=${encodeURIComponent('1,40-60,30-50,tlshello')}` : config_JSON.tlsFragment == 'Happ' ? `&fragment=${encodeURIComponent('3,1,tlshello')}` : ''; + if (!config_JSON.Fingerprint) config_JSON.Fingerprint = "chrome"; + if (!config_JSON.ECH) config_JSON.ECH = false; + else config_JSON.subGenerator.SUBUpdateTime = 1; // enable ECH forceChangesubupdatetimechangeTo 1 hour + const ECHLINKparam = config_JSON.ECH ? `&ech=${encodeURIComponent('cloudflare-ech.com+' + ECH_DOH)}` : ''; + config_JSON.LINK = x27cnEnc(`${config_JSON.protocolType}://${userID}@${host}:443?security=tls&type=${config_JSON.transport + ECHLINKparam}&host=${host}&fp=${config_JSON.Fingerprint}&sni=${host}&path=${encodeURIComponent(config_JSON.enable0RTT ? config_JSON.PATH + '?ed=2560' : config_JSON.PATH) + tlsFragmentparam}&encryption=none${config_JSON.skipCertVerify ? '&insecure=1&allowInsecure=1' : ''}#${encodeURIComponent(config_JSON.subGenerator.SUBNAME)}`); + config_JSON.subGenerator.TOKEN = await MD5MD5(hostname + userID); + + const initTG_JSON = { BotToken: null, ChatID: null }; + config_JSON.TG = { enable: config_JSON.TG.enable ? config_JSON.TG.enable : false, ...initTG_JSON }; + try { + const TG_TXT = await env.KV.get('tg.json'); + if (!TG_TXT) { + await env.KV.put('tg.json', JSON.stringify(initTG_JSON, null, 2)); + } else { + const TG_JSON = JSON.parse(TG_TXT); + config_JSON.TG.ChatID = TG_JSON.ChatID ? TG_JSON.ChatID : null; + config_JSON.TG.BotToken = TG_JSON.BotToken ? maskSensitive(TG_JSON.BotToken) : null; + } + } catch (error) { + console.error(`readtg.jsonerror: ${error.message}`); + } + + const initCF_JSON = { Email: null, GlobalAPIKey: null, AccountID: null, APIToken: null, UsageAPI: null }; + config_JSON.CF = { ...initCF_JSON, Usage: { success: false, pages: 0, workers: 0, total: 0, max: 100000 } }; + try { + const CF_TXT = await env.KV.get('cf.json'); + if (!CF_TXT) { + await env.KV.put('cf.json', JSON.stringify(initCF_JSON, null, 2)); + } else { + const CF_JSON = JSON.parse(CF_TXT); + if (CF_JSON.UsageAPI) { + try { + const response = await fetch(CF_JSON.UsageAPI); + const Usage = await response.json(); + config_JSON.CF.Usage = Usage; + } catch (err) { + console.error(`request CF_JSON.UsageAPI failed: ${err.message}`); + } + } else { + config_JSON.CF.Email = CF_JSON.Email ? CF_JSON.Email : null; + config_JSON.CF.GlobalAPIKey = CF_JSON.GlobalAPIKey ? maskSensitive(CF_JSON.GlobalAPIKey) : null; + config_JSON.CF.AccountID = CF_JSON.AccountID ? maskSensitive(CF_JSON.AccountID) : null; + config_JSON.CF.APIToken = CF_JSON.APIToken ? maskSensitive(CF_JSON.APIToken) : null; + config_JSON.CF.UsageAPI = null; + const Usage = await getCloudflareUsage(CF_JSON.Email, CF_JSON.GlobalAPIKey, CF_JSON.AccountID, CF_JSON.APIToken); + config_JSON.CF.Usage = Usage; + } + } + } catch (error) { + console.error(`readcf.jsonerror: ${error.message}`); + } + + config_JSON.loadtime = (performance.now() - initStarttime).toFixed(2) + 'ms'; + return config_JSON; +} + +async function genrandomIP(request, count = 16, specifiedPort = -1) { + const asnMap = { '9808': 'cmcc', '4837': 'cu', '4134': 'ct' }, asn = request.cf.asn; + const cidr_url = asnMap[asn] ? `https://raw.githubusercontent.com/cmliu/cmliu/main/CF-CIDR/${asnMap[asn]}.txt` : 'https://raw.githubusercontent.com/cmliu/cmliu/main/CF-CIDR.txt'; + const cfname = { '9808': 'CFCMCCOptimal', '4837': 'CFCUOptimal', '4134': 'CFCTOptimal' }[asn] || 'CFofficialOptimal'; + const cfport = [443, 2053, 2083, 2087, 2096, 8443]; + let cidrList = []; + try { const res = await fetch(cidr_url); cidrList = res.ok ? await parseToArray(await res.text()) : ['104.16.0.0/13']; } catch { cidrList = ['104.16.0.0/13']; } + + const generateRandomIPFromCIDR = (cidr) => { + const [baseIP, prefixLength] = cidr.split('/'), prefix = parseInt(prefixLength), hostBits = 32 - prefix; + const ipInt = baseIP.split('.').reduce((a, p, i) => a | (parseInt(p) << (24 - i * 8)), 0); + const randomOffset = Math.floor(Math.random() * Math.pow(2, hostBits)); + const mask = (0xFFFFFFFF << hostBits) >>> 0, randomIP = (((ipInt & mask) >>> 0) + randomOffset) >>> 0; + return [(randomIP >>> 24) & 0xFF, (randomIP >>> 16) & 0xFF, (randomIP >>> 8) & 0xFF, randomIP & 0xFF].join('.'); + }; + + const randomIPs = Array.from({ length: count }, () => { + const ip = generateRandomIPFromCIDR(cidrList[Math.floor(Math.random() * cidrList.length)]); + return `${ip}:${specifiedPort === -1 ? cfport[Math.floor(Math.random() * cfport.length)] : specifiedPort}#${cfname}`; + }); + return [randomIPs, randomIPs.join('\n')]; +} + +async function parseToArray(content) { + var replaced = content.replace(/[ "'\r\n]+/g, ',').replace(/,+/g, ','); + if (replaced.charAt(0) == ',') replaced = replaced.slice(1); + if (replaced.charAt(replaced.length - 1) == ',') replaced = replaced.slice(0, replaced.length - 1); + const addrArr = replaced.split(','); + return addrArr; +} + +function isValidBase64(str) { + if (typeof str !== 'string') return false; + const cleanStr = str.replace(/\s/g, ''); + if (cleanStr.length === 0 || cleanStr.length % 4 !== 0) return false; + const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/; + if (!base64Regex.test(cleanStr)) return false; + try { + atob(cleanStr); + return true; + } catch { + return false; + } +} + +function base64Decode(str) { + const bytes = new Uint8Array(atob(str).split('').map(c => c.charCodeAt(0))); + const decoder = new TextDecoder('utf-8'); + return decoder.decode(bytes); +} + +async function requestOptimalAPI(urls, defPort = '443', timeouttime = 3000) { + if (!urls?.length) return [[], [], []]; + const results = new Set(); + let subLinkPlainLINKcontent = '', needSubConvertURLs = []; + await Promise.allSettled(urls.map(async (url) => { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeouttime); + const response = await fetch(url, { signal: controller.signal }); + clearTimeout(timeoutId); + let text = ''; + try { + const buffer = await response.arrayBuffer(); + const contentType = (response.headers.get('content-type') || '').toLowerCase(); + const charset = contentType.match(/charset=([^\s;]+)/i)?.[1]?.toLowerCase() || ''; + + + let decoders = ['utf-8', 'gb2312']; // defFirst UTF-8 + if (charset.includes('gb') || charset.includes('gbk') || charset.includes('gb2312')) { + decoders = ['gb2312', 'utf-8']; // likeifSpecified GB encoding,tryFirst GB2312 + } + + + let decodeSuccess = false; + for (const decoder of decoders) { + try { + const decoded = new TextDecoder(decoder).decode(buffer); + + if (decoded && decoded.length > 0 && !decoded.includes('\ufffd')) { + text = decoded; + decodeSuccess = true; + break; + } else if (decoded && decoded.length > 0) { + + continue; + } + } catch (e) { + + continue; + } + } + + + if (!decodeSuccess) { + text = await response.text(); + } + + + if (!text || text.trim().length === 0) { + return; + } + } catch (e) { + console.error('Failed to decode response:', e); + return; + } + + + + + const preSubPlain = isValidBase64(text) ? base64Decode(text) : text; + if (preSubPlain.split('#')[0].includes('://')) { + subLinkPlainLINKcontent += preSubPlain + '\n'; // appendLINKplaincontent + return; + } + + const lines = text.trim().split('\n').map(l => l.trim()).filter(l => l); + const isCSV = lines.length > 1 && lines[0].includes(','); + const IPV6_PATTERN = /^[^\[\]]*:[^\[\]]*:[^\[\]]/; + if (!isCSV) { + lines.forEach(line => { + const hashIndex = line.indexOf('#'); + const [hostPart, remark] = hashIndex > -1 ? [line.substring(0, hashIndex), line.substring(hashIndex)] : [line, '']; + let hasPort = false; + if (hostPart.startsWith('[')) { + hasPort = /\]:(\d+)$/.test(hostPart); + } else { + const colonIndex = hostPart.lastIndexOf(':'); + hasPort = colonIndex > -1 && /^\d+$/.test(hostPart.substring(colonIndex + 1)); + } + const port = new URL(url).searchParams.get('port') || defPort; + results.add(hasPort ? line : `${hostPart}:${port}${remark}`); + }); + } else { + const headers = lines[0].split(',').map(h => h.trim()); + const dataLines = lines.slice(1); + if (headers.includes('IPaddr') && headers.includes('port') && headers.includes('dataCenter')) { + const ipIdx = headers.indexOf('IPaddr'), portIdx = headers.indexOf('port'); + const remarkIdx = headers.indexOf('country') > -1 ? headers.indexOf('country') : + headers.indexOf('city') > -1 ? headers.indexOf('city') : headers.indexOf('dataCenter'); + const tlsIdx = headers.indexOf('TLS'); + dataLines.forEach(line => { + const cols = line.split(',').map(c => c.trim()); + if (tlsIdx !== -1 && cols[tlsIdx]?.toLowerCase() !== 'true') return; + const wrappedIP = IPV6_PATTERN.test(cols[ipIdx]) ? `[${cols[ipIdx]}]` : cols[ipIdx]; + results.add(`${wrappedIP}:${cols[portIdx]}#${cols[remarkIdx]}`); + }); + } else if (headers.some(h => h.includes('IP')) && headers.some(h => h.includes('latency')) && headers.some(h => h.includes('dlSpeed'))) { + const ipIdx = headers.findIndex(h => h.includes('IP')); + const delayIdx = headers.findIndex(h => h.includes('latency')); + const speedIdx = headers.findIndex(h => h.includes('dlSpeed')); + const port = new URL(url).searchParams.get('port') || defPort; + dataLines.forEach(line => { + const cols = line.split(',').map(c => c.trim()); + const wrappedIP = IPV6_PATTERN.test(cols[ipIdx]) ? `[${cols[ipIdx]}]` : cols[ipIdx]; + results.add(`${wrappedIP}:${port}#CFOptimal ${cols[delayIdx]}ms ${cols[speedIdx]}MB/s`); + }); + } + } + } catch (e) { } + })); + + const LINKarr = subLinkPlainLINKcontent.trim() ? [...new Set(subLinkPlainLINKcontent.split(/\r?\n/).filter(line => line.trim() !== ''))] : []; + return [Array.from(results), LINKarr, needSubConvertURLs]; +} + +async function reverseProxyparamget(request) { + const url = new URL(request.url); + const { pathname, searchParams } = url; + const pathLower = pathname.toLowerCase(); + + + mySOCKS5Account = searchParams.get('socks5') || searchParams.get('http') || null; + enableGlobalSOCKS5 = searchParams.has('globalproxy') || false; + + + const proxyMatch = pathLower.match(/\/(proxyip[.=]|pyip=|ip=)(.+)/); + if (searchParams.has('proxyip')) { + const pathParamIP = searchParams.get('proxyip'); + proxyIP = pathParamIP.includes(',') ? pathParamIP.split(',')[Math.floor(Math.random() * pathParamIP.split(',').length)] : pathParamIP; + enableProxyFallback = false; + return; + } else if (proxyMatch) { + const pathParamIP = proxyMatch[1] === 'proxyip.' ? `proxyip.${proxyMatch[2]}` : proxyMatch[2]; + proxyIP = pathParamIP.includes(',') ? pathParamIP.split(',')[Math.floor(Math.random() * pathParamIP.split(',').length)] : pathParamIP; + enableProxyFallback = false; + return; + } + + + let socksMatch; + if ((socksMatch = pathname.match(/\/(socks5?|http):\/?\/?(.+)/i))) { + + enableSOCKS5Proxy = socksMatch[1].toLowerCase() === 'http' ? 'http' : 'socks5'; + mySOCKS5Account = socksMatch[2].split('#')[0]; + enableGlobalSOCKS5 = true; + + + if (mySOCKS5Account.includes('@')) { + const atIndex = mySOCKS5Account.lastIndexOf('@'); + let userPassword = mySOCKS5Account.substring(0, atIndex).replaceAll('%3D', '='); + if (/^(?:[A-Z0-9+/]{4})*(?:[A-Z0-9+/]{2}==|[A-Z0-9+/]{3}=)?$/i.test(userPassword) && !userPassword.includes(':')) { + userPassword = atob(userPassword); + } + mySOCKS5Account = `${userPassword}@${mySOCKS5Account.substring(atIndex + 1)}`; + } + } else if ((socksMatch = pathname.match(/\/(g?s5|socks5|g?http)=(.+)/i))) { + + const type = socksMatch[1].toLowerCase(); + mySOCKS5Account = socksMatch[2]; + enableSOCKS5Proxy = type.includes('http') ? 'http' : 'socks5'; + enableGlobalSOCKS5 = type.startsWith('g') || enableGlobalSOCKS5; // gs5 or ghttp startenableglobalMode + } + + + if (mySOCKS5Account) { + try { + parsedSocks5Address = await getSOCKS5account(mySOCKS5Account); + enableSOCKS5Proxy = searchParams.get('http') ? 'http' : enableSOCKS5Proxy; + } catch (err) { + console.error('parseSOCKS5addrFailed:', err.message); + enableSOCKS5Proxy = null; + } + } else enableSOCKS5Proxy = null; +} + +async function getSOCKS5account(address) { + if (address.includes('@')) { + const lastAtIndex = address.lastIndexOf('@'); + let userPassword = address.substring(0, lastAtIndex).replaceAll('%3D', '='); + const base64Regex = /^(?:[A-Z0-9+/]{4})*(?:[A-Z0-9+/]{2}==|[A-Z0-9+/]{3}=)?$/i; + if (base64Regex.test(userPassword) && !userPassword.includes(':')) userPassword = atob(userPassword); + address = `${userPassword}@${address.substring(lastAtIndex + 1)}`; + } + const atIndex = address.lastIndexOf("@"); + const [hostPart, authPart] = atIndex === -1 ? [address, undefined] : [address.substring(atIndex + 1), address.substring(0, atIndex)]; + + + let username, password; + if (authPart) { + [username, password] = authPart.split(":"); + if (!password) throw new Error('invalid SOCKS addrFormat:authMustBe "username:password" format'); + } + + + let hostname, port; + if (hostPart.includes("]:")) { // IPv6withport + [hostname, port] = [hostPart.split("]:")[0] + "]", Number(hostPart.split("]:")[1].replace(/[^\d]/g, ''))]; + } else if (hostPart.startsWith("[")) { // IPv6noneport + [hostname, port] = [hostPart, 80]; + } else { // IPv4/domain + const parts = hostPart.split(":"); + [hostname, port] = parts.length === 2 ? [parts[0], Number(parts[1].replace(/[^\d]/g, ''))] : [hostPart, 80]; + } + + if (isNaN(port)) throw new Error('invalid SOCKS addrFormat:portMustBeNumber'); + if (hostname.includes(":") && !/^\[.*\]$/.test(hostname)) throw new Error('invalid SOCKS addrFormat:IPv6 addrMustBeBracketed,like [2001:db8::1]'); + + return { username, password, hostname, port }; +} + +async function getCloudflareUsage(Email, GlobalAPIKey, AccountID, APIToken) { + const API = "https://api.cloudflare.com/client/v4"; + const sum = (a) => a?.reduce((t, i) => t + (i?.sum?.requests || 0), 0) || 0; + const cfg = { "Content-Type": "application/json" }; + + try { + if (!AccountID && (!Email || !GlobalAPIKey)) return { success: false, pages: 0, workers: 0, total: 0, max: 100000 }; + + if (!AccountID) { + const r = await fetch(`${API}/accounts`, { + method: "GET", + headers: { ...cfg, "X-AUTH-EMAIL": Email, "X-AUTH-KEY": GlobalAPIKey } + }); + if (!r.ok) throw new Error(`accountGetFailed: ${r.status}`); + const d = await r.json(); + if (!d?.result?.length) throw new Error("accountNotFound"); + const idx = d.result.findIndex(a => a.name?.toLowerCase().startsWith(Email.toLowerCase())); + AccountID = d.result[idx >= 0 ? idx : 0]?.id; + } + + const now = new Date(); + now.setUTCHours(0, 0, 0, 0); + const hdr = APIToken ? { ...cfg, "Authorization": `Bearer ${APIToken}` } : { ...cfg, "X-AUTH-EMAIL": Email, "X-AUTH-KEY": GlobalAPIKey }; + + const res = await fetch(`${API}/graphql`, { + method: "POST", + headers: hdr, + body: JSON.stringify({ + query: `query getBillingMetrics($AccountID: String!, $filter: AccountWorkersInvocationsAdaptiveFilter_InputObject) { + viewer { accounts(filter: {accountTag: $AccountID}) { + pagesFunctionsInvocationsAdaptiveGroups(limit: 1000, filter: $filter) { sum { requests } } + workersInvocationsAdaptive(limit: 10000, filter: $filter) { sum { requests } } + } } + }`, + variables: { AccountID, filter: { datetime_geq: now.toISOString(), datetime_leq: new Date().toISOString() } } + }) + }); + + if (!res.ok) throw new Error(`queryFailed: ${res.status}`); + const result = await res.json(); + if (result.errors?.length) throw new Error(result.errors[0].message); + + const acc = result?.data?.viewer?.accounts?.[0]; + if (!acc) throw new Error("accountDataNotFound"); + + const pages = sum(acc.pagesFunctionsInvocationsAdaptiveGroups); + const workers = sum(acc.workersInvocationsAdaptive); + const total = pages + workers; + const max = 100000; + console.log(`statResult - Pages: ${pages}, Workers: ${workers}, totalCnt: ${total}, limit: 100000`); + return { success: true, pages, workers, total, max }; + + } catch (error) { + console.error('getUsageError:', error.message); + return { success: false, pages: 0, workers: 0, total: 0, max: 100000 }; + } +} + +function sha224(s) { + const K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]; + const r = (n, b) => ((n >>> b) | (n << (32 - b))) >>> 0; + s = unescape(encodeURIComponent(s)); + const l = s.length * 8; s += String.fromCharCode(0x80); + while ((s.length * 8) % 512 !== 448) s += String.fromCharCode(0); + const h = [0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4]; + const hi = Math.floor(l / 0x100000000), lo = l & 0xFFFFFFFF; + s += String.fromCharCode((hi >>> 24) & 0xFF, (hi >>> 16) & 0xFF, (hi >>> 8) & 0xFF, hi & 0xFF, (lo >>> 24) & 0xFF, (lo >>> 16) & 0xFF, (lo >>> 8) & 0xFF, lo & 0xFF); + const w = []; for (let i = 0; i < s.length; i += 4)w.push((s.charCodeAt(i) << 24) | (s.charCodeAt(i + 1) << 16) | (s.charCodeAt(i + 2) << 8) | s.charCodeAt(i + 3)); + for (let i = 0; i < w.length; i += 16) { + const x = new Array(64).fill(0); + for (let j = 0; j < 16; j++)x[j] = w[i + j]; + for (let j = 16; j < 64; j++) { + const s0 = r(x[j - 15], 7) ^ r(x[j - 15], 18) ^ (x[j - 15] >>> 3); + const s1 = r(x[j - 2], 17) ^ r(x[j - 2], 19) ^ (x[j - 2] >>> 10); + x[j] = (x[j - 16] + s0 + x[j - 7] + s1) >>> 0; + } + let [a, b, c, d, e, f, g, h0] = h; + for (let j = 0; j < 64; j++) { + const S1 = r(e, 6) ^ r(e, 11) ^ r(e, 25), ch = (e & f) ^ (~e & g), t1 = (h0 + S1 + ch + K[j] + x[j]) >>> 0; + const S0 = r(a, 2) ^ r(a, 13) ^ r(a, 22), maj = (a & b) ^ (a & c) ^ (b & c), t2 = (S0 + maj) >>> 0; + h0 = g; g = f; f = e; e = (d + t1) >>> 0; d = c; c = b; b = a; a = (t1 + t2) >>> 0; + } + for (let j = 0; j < 8; j++)h[j] = (h[j] + (j === 0 ? a : j === 1 ? b : j === 2 ? c : j === 3 ? d : j === 4 ? e : j === 5 ? f : j === 6 ? g : h0)) >>> 0; + } + let hex = ''; + for (let i = 0; i < 7; i++) { + for (let j = 24; j >= 0; j -= 8)hex += ((h[i] >>> j) & 0xFF).toString(16).padStart(2, '0'); + } + return hex; +} + +async function parseAddrPort(proxyIP, targetdomain = 'dash.cloudflare.com', UUID = '00000000-0000-4000-8000-000000000000') { + if (!cachedProxyIP || !cachedProxyArray || cachedProxyIP !== proxyIP) { + proxyIP = proxyIP.toLowerCase(); + async function DoHquery(domain, recordtype) { + try { + const response = await fetch(`https://1.1.1.1/dns-query?name=${domain}&type=${recordtype}`, { + headers: { 'Accept': 'application/dns-json' } + }); + if (!response.ok) return []; + const data = await response.json(); + return data.Answer || []; + } catch (error) { + console.error(`dohQueryFailed (${recordtype}):`, error); + return []; + } + } + + function parseAddrPortStr(str) { + let addr = str, port = 443; + if (str.includes(']:')) { + const parts = str.split(']:'); + addr = parts[0] + ']'; + port = parseInt(parts[1], 10) || port; + } else if (str.includes(':') && !str.startsWith('[')) { + const colonIndex = str.lastIndexOf(':'); + addr = str.slice(0, colonIndex); + port = parseInt(str.slice(colonIndex + 1), 10) || port; + } + return [addr, port]; + } + + let allreverseProxyarr = []; + + if (proxyIP.includes('.william')) { + try { + const txtRecords = await DoHquery(proxyIP, 'TXT'); + const txtData = txtRecords.filter(r => r.type === 16).map(r => r.data); + if (txtData.length > 0) { + let data = txtData[0]; + if (data.startsWith('"') && data.endsWith('"')) data = data.slice(1, -1); + const prefixes = data.replace(/\\010/g, ',').replace(/\n/g, ',').split(',').map(s => s.trim()).filter(Boolean); + allreverseProxyarr = prefixes.map(prefix => parseAddrPortStr(prefix)); + } + } catch (error) { + console.error('parseWilliamFailed:', error); + } + } else { + let [addr, port] = parseAddrPortStr(proxyIP); + + if (proxyIP.includes('.tp')) { + const tpMatch = proxyIP.match(/\.tp(\d+)/); + if (tpMatch) port = parseInt(tpMatch[1], 10); + } + + + const ipv4Regex = /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/; + const ipv6Regex = /^\[?([a-fA-F0-9:]+)\]?$/; + + if (!ipv4Regex.test(addr) && !ipv6Regex.test(addr)) { + + const [aRecords, aaaaRecords] = await Promise.all([ + DoHquery(addr, 'A'), + DoHquery(addr, 'AAAA') + ]); + + const ipv4List = aRecords.filter(r => r.type === 1).map(r => r.data); + const ipv6List = aaaaRecords.filter(r => r.type === 28).map(r => `[${r.data}]`); + const ipAddresses = [...ipv4List, ...ipv6List]; + + allreverseProxyarr = ipAddresses.length > 0 + ? ipAddresses.map(ip => [ip, port]) + : [[addr, port]]; + } else { + allreverseProxyarr = [[addr, port]]; + } + } + const sortedArr = allreverseProxyarr.sort((a, b) => a[0].localeCompare(b[0])); + const targetRootdomain = targetdomain.includes('.') ? targetdomain.split('.').slice(-2).join('.') : targetdomain; + let randSeed = [...(targetRootdomain + UUID)].reduce((a, c) => a + c.charCodeAt(0), 0); + console.log(`[proxyParse] randSeed: ${randSeed}\ntargetSite: ${targetRootdomain}`) + const shuffled = [...sortedArr].sort(() => (randSeed = (randSeed * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff - 0.5); + cachedProxyArray = shuffled.slice(0, 8); + console.log(`[proxyParse] parseComplete total: ${cachedProxyArray.length}\n${cachedProxyArray.map(([ip, port], index) => `${index + 1}. ${ip}:${port}`).join('\n')}`); + cachedProxyIP = proxyIP; + } else console.log(`[proxyParse] readCache total: ${cachedProxyArray.length}\n${cachedProxyArray.map(([ip, port], index) => `${index + 1}. ${ip}:${port}`).join('\n')}`); + return cachedProxyArray; +} + +async function SOCKS5availabilityCheck(proxyProto = 'socks5', proxyparam) { + const startTime = Date.now(); + try { parsedSocks5Address = await getSOCKS5account(proxyparam); } catch (err) { return { success: false, error: err.message, proxy: proxyProto + "://" + proxyparam, responseTime: Date.now() - startTime }; } + const { username, password, hostname, port } = parsedSocks5Address; + const fullProxyparam = username && password ? `${username}:${password}@${hostname}:${port}` : `${hostname}:${port}`; + try { + const initialData = new Uint8Array(0); + const tcpSocket = proxyProto == 'socks5' ? await socks5Connect('check.socks5.090227.xyz', 80, initialData) : await httpConnect('check.socks5.090227.xyz', 80, initialData); + if (!tcpSocket) return { success: false, error: 'cannotConnectToProxy', proxy: proxyProto + "://" + fullProxyparam, responseTime: Date.now() - startTime }; + try { + const writer = tcpSocket.writable.getWriter(), encoder = new TextEncoder(); + await writer.write(encoder.encode(`GET /cdn-cgi/trace HTTP/1.1\r\nHost: check.socks5.090227.xyz\r\nConnection: close\r\n\r\n`)); + writer.releaseLock(); + const reader = tcpSocket.readable.getReader(), decoder = new TextDecoder(); + let response = ''; + try { while (true) { const { done, value } = await reader.read(); if (done) break; response += decoder.decode(value, { stream: true }); } } finally { reader.releaseLock(); } + await tcpSocket.close(); + return { success: true, proxy: proxyProto + "://" + fullProxyparam, ip: response.match(/ip=(.*)/)[1], loc: response.match(/loc=(.*)/)[1], responseTime: Date.now() - startTime }; + } catch (error) { + try { await tcpSocket.close(); } catch (e) { console.log('errorClosingConn:', e); } + return { success: false, error: error.message, proxy: proxyProto + "://" + fullProxyparam, responseTime: Date.now() - startTime }; + } + } catch (error) { return { success: false, error: error.message, proxy: proxyProto + "://" + fullProxyparam, responseTime: Date.now() - startTime }; } +} + +async function nginx() { + return ` + + + + Welcome to nginx! + + + +

Welcome to nginx!

+

If you see this page, the nginx web server is successfully installed and + working. Further configuration is required.

+ +

For online documentation and support please refer to + nginx.org.
+ Commercial support is available at + nginx.com.

+ +

Thank you for using nginx.

+ + + ` +} + +async function html1101(host, clientIP) { + const now = new Date(); + const formattimestamp = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') + ':' + String(now.getSeconds()).padStart(2, '0'); + const randStr = Array.from(crypto.getRandomValues(new Uint8Array(8))).map(b => b.toString(16).padStart(2, '0')).join(''); + + return ` + + + + + +Worker threw exception | ${host} | Cloudflare + + + + + + + + + + + + + + + + +
+ +
+
+

+ Error + 1101 + Ray ID: ${randStr} • ${formattimestamp} UTC +

+

Worker threw exception

+
+ +
+ +
+
+
+

What happened?

+

You've requested a page on a website (${host}) that is on the Cloudflare network. An unknown error occurred while rendering the page.

+
+ +
+

What can I do?

+

If you are the owner of this website:
refer to Workers - Errors and Exceptions and check Workers Logs for ${host}.

+
+ +
+
+ + + +
+
+ + + +`; +} + diff --git a/workers_translated.js b/workers_translated.js new file mode 100644 index 0000000..9d6409c --- /dev/null +++ b/workers_translated.js @@ -0,0 +1,2426 @@ +import { connect } from "cloudflare:sockets"; +let config_JSON, proxyIP = '', enableSOCKS5Proxy = null, enableGlobalSOCKS5 = false, mySOCKS5Account = '', parsedSocks5Address = {}; +let cachedProxyIP, cachedProxyArray, cachedProxyIndex = 0, enableProxyFallback = true, ECH_DOH = 'https://doh.cmliussss.net/CMLiussss'; +let socks5Whitelist = ['*tapecontent.net', '*cloudatacdn.com', '*loadshare.org', '*cdn-centaurus.com', 'scholar.google.com']; +const pagesStaticUrl = 'https://edt-pages.github.io'; + +export default { + async fetch(request, env, ctx) { + const url = new URL(request.url); + const UA = request.headers.get('User-Agent') || 'null'; + const upgradeHeader = request.headers.get('Upgrade'); + + const PUBLIC_UUID = 'pub-node-00000000-0000'; // public UUID prefix + const adminPassword = env.ADMIN || env.admin || env.PASSWORD || env.password || env.pswd || env.TOKEN || env.KEY || env.UUID || env.uuid || 'cfspider-public'; + const encryptKey = env.KEY || 'cfspider-default-key'; + const userIDMD5 = await MD5MD5(adminPassword + encryptKey); + const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/; + const envUUID = env.UUID || env.uuid; + const userID = (envUUID && uuidRegex.test(envUUID)) ? envUUID.toLowerCase() : [userIDMD5.slice(0, 8), userIDMD5.slice(8, 12), '4' + userIDMD5.slice(13, 16), '8' + userIDMD5.slice(17, 20), userIDMD5.slice(20)].join('-'); + const hosts = env.HOST ? (await parseToArray(env.HOST)).map(h => h.toLowerCase().replace(/^https?:\/\//, '').split('/')[0].split(':')[0]) : [url.hostname]; + const host = hosts[0]; + if (env.PROXYIP) { + const proxyIPs = await parseToArray(env.PROXYIP); + proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; + enableProxyFallback = false; + } else proxyIP = (request.cf.colo + '.PrOxYIp.CmLiUsSsS.nEt').toLowerCase(); + const clientIP = request.headers.get('X-Real-IP') || request.headers.get('CF-Connecting-IP') || request.headers.get('X-Forwarded-For') || request.headers.get('True-Client-IP') || request.headers.get('Fly-Client-IP') || request.headers.get('X-Appengine-Remote-Addr') || request.headers.get('X-Forwarded-For') || request.headers.get('X-Real-IP') || request.headers.get('X-Cluster-Client-IP') || request.cf?.clientTcpRtt || 'unknownIP'; + if (env.GO2SOCKS5) socks5Whitelist = await parseToArray(env.GO2SOCKS5); + ECH_DOH = env.ECH_DOH || env.DOH || ECH_DOH; + if (!upgradeHeader || upgradeHeader !== 'websocket') { + if (url.protocol === 'http:') return Response.redirect(url.href.replace(`http://${url.hostname}`, `https://${url.hostname}`), 301); + + + const apiPath = url.pathname.slice(1).toLowerCase(); + const newIpEnabled = env.NEW_IP !== 'false' && env.NEW_IP !== '0'; + + const isDefaultUUID = !envUUID || !uuidRegex.test(envUUID); + + const twoProxy = env.TWO_PROXY || env.two_proxy || ''; + if (apiPath === '' || apiPath === '/') { + const colo = request.cf?.colo || 'UNKNOWN'; + const vlessPath = '/' + userID + (twoProxy ? '?two_proxy=' + encodeURIComponent(twoProxy) : ''); + const vlessLink = 'vless://' + userID + '@' + url.hostname + ':443?security=tls&type=ws&host=' + url.hostname + '&sni=' + url.hostname + '&path=' + encodeURIComponent(vlessPath) + '&encryption=none#Node-' + colo; + return new Response(JSON.stringify({ + status: 'online', + version: '1.8.7', + colo: colo, + host: url.hostname, + uuid: userID, + vless: vlessLink, + two_proxy: twoProxy || null + }, null, 2), { + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } + }); + } + + if (apiPath === 'api/proxyip') { + const colo = request.cf?.colo || 'UNKNOWN'; + const defaultProxyIp = (colo + '.proxyip.cmliussss.net').toLowerCase(); + const envProxyIp = env.PROXYIP || ''; + return new Response(JSON.stringify({ + colo: colo, + default: defaultProxyIp, + hk: 'proxyip.cfspider.com', + env: envProxyIp || null, + current: envProxyIp || defaultProxyIp, + options: { + default: { name: 'nlNode', address: defaultProxyIp }, + hk: { name: 'hkNode', address: 'proxyip.cfspider.com' } + } + }), { + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + } + }); + } + + if (apiPath === 'api/uuid' || apiPath === 'api/config') { + + const newIpEnabled = env.NEW_IP !== 'false' && env.NEW_IP !== '0'; + + const twoProxyConfig = env.TWO_PROXY || env.two_proxy || ''; + + const configResponse = { + host: url.hostname, + new_ip: newIpEnabled, + version: '1.8.7', + is_default_uuid: isDefaultUUID, + two_proxy_enabled: !!twoProxyConfig + }; + + if (isDefaultUUID) { + configResponse.uuid = userID; + configResponse.vless_path = twoProxyConfig + ? '/' + userID + '?two_proxy=' + encodeURIComponent(twoProxyConfig) + : '/' + userID; + } else { + + if (twoProxyConfig) { + configResponse.two_proxy = twoProxyConfig; // returnFulltwoProxyconfig + } + } + + if (twoProxyConfig) { + const parts = twoProxyConfig.split(':'); + configResponse.two_proxy_host = parts[0] || ''; + configResponse.two_proxy_port = parts[1] || ''; + } + return new Response(JSON.stringify(configResponse), { + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + } + }); + } + + if (apiPath === 'proxy' || apiPath.startsWith('proxy?')) { + const targetUrl = url.searchParams.get('url'); + const method = url.searchParams.get('method') || 'GET'; + const twoProxyParam = url.searchParams.get('two_proxy'); + + if (!targetUrl) { + return new Response(JSON.stringify({error: 'Missing url parameter'}), { + status: 400, + headers: {'Content-Type': 'application/json'} + }); + } + + try { + + const proxyHeaders = {}; + for (const [key, value] of request.headers) { + if (key.toLowerCase().startsWith('x-custom-header-')) { + const originalKey = key.substring(18); // remove 'x-custom-header-' prefix + proxyHeaders[originalKey] = value; + } + } + + let response; + + + const twoProxy = twoProxyParam || env.TWO_PROXY || env.two_proxy || ''; + + if (twoProxy) { + + const proxyParts = twoProxy.split(':'); + const proxyHost = proxyParts[0]; + const proxyPort = parseInt(proxyParts[1]) || 3128; + const proxyUser = proxyParts[2] || ''; + const proxyPass = proxyParts[3] || ''; + + + const targetParsed = new URL(targetUrl); + const targetHost = targetParsed.hostname; + const targetPort = targetParsed.port || (targetParsed.protocol === 'https:' ? 443 : 80); + const isHttps = targetParsed.protocol === 'https:'; + + + const { connect } = await import('cloudflare:sockets'); + + if (isHttps) { + + return new Response(JSON.stringify({ + error: 'HTTPS + two_proxy notSupportedViaProxyAPI。useCfspiderGetWithTwoProxy。', + hint: 'client.get(url, cf_proxies=..., uuid=..., two_proxy=...)', + reason: 'Workers /proxy API onlyHTTPTwoProxySupported' + }), { + status: 501, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + } + }); + } else { + + const socket = connect({ + hostname: proxyHost, + port: proxyPort + }); + + const writer = socket.writable.getWriter(); + const reader = socket.readable.getReader(); + + + let httpReq = `${method} ${targetUrl} HTTP/1.1\r\nHost: ${targetHost}\r\n`; + if (proxyUser && proxyPass) { + const auth = btoa(`${proxyUser}:${proxyPass}`); + httpReq += `Proxy-Authorization: Basic ${auth}\r\n`; + } + for (const [key, value] of Object.entries(proxyHeaders)) { + httpReq += `${key}: ${value}\r\n`; + } + httpReq += 'Connection: close\r\n\r\n'; + + await writer.write(new TextEncoder().encode(httpReq)); + + + let responseData = new Uint8Array(0); + while (true) { + const { value, done } = await reader.read(); + if (done) break; + const newData = new Uint8Array(responseData.length + value.length); + newData.set(responseData); + newData.set(value, responseData.length); + responseData = newData; + } + + + const responseText = new TextDecoder().decode(responseData); + const headerEnd = responseText.indexOf('\r\n\r\n'); + const headers = responseText.substring(0, headerEnd); + const body = responseData.slice(new TextEncoder().encode(responseText.substring(0, headerEnd + 4)).length); + + const statusLine = headers.split('\r\n')[0]; + const statusCode = parseInt(statusLine.split(' ')[1]) || 200; + + const responseHeaders = new Headers(); + headers.split('\r\n').slice(1).forEach(line => { + const [key, ...valueParts] = line.split(':'); + if (key && valueParts.length) { + responseHeaders.set(key.trim(), valueParts.join(':').trim()); + } + }); + responseHeaders.set('Access-Control-Allow-Origin', '*'); + responseHeaders.set('X-CF-Colo', request.cf?.colo || 'unknown'); + responseHeaders.set('X-Worker-Version', '1.8.6'); + responseHeaders.set('X-Two-Proxy', 'enabled'); + + return new Response(body, { + status: statusCode, + headers: responseHeaders + }); + } + } else { + + const proxyRequest = new Request(targetUrl, { + method: method, + headers: proxyHeaders, + body: method !== 'GET' && method !== 'HEAD' ? request.body : null + }); + + response = await fetch(proxyRequest); + + + const responseHeaders = new Headers(response.headers); + responseHeaders.set('Access-Control-Allow-Origin', '*'); + responseHeaders.set('X-CF-Colo', request.cf?.colo || 'unknown'); + responseHeaders.set('X-Worker-Version', '1.8.6'); + + return new Response(response.body, { + status: response.status, + headers: responseHeaders + }); + } + } catch (error) { + return new Response(JSON.stringify({error: error.message}), { + status: 500, + headers: {'Content-Type': 'application/json'} + }); + } + } + + + if (apiPath === 'api/config/new_ip' && request.method === 'POST') { + + const newIpEnabled = env.NEW_IP !== 'false' && env.NEW_IP !== '0'; + return new Response(JSON.stringify({ + new_ip: newIpEnabled, + message: 'setNEWIPViaDashboard' + }), { + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + } + }); + } + + if (!adminPassword) return fetch(pagesStaticUrl + '/noADMIN').then(r => { const headers = new Headers(r.headers); headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); headers.set('Pragma', 'no-cache'); headers.set('Expires', '0'); return new Response(r.body, { status: 404, statusText: r.statusText, headers }); }); + if (env.KV && typeof env.KV.get === 'function') { + const visitpath = url.pathname.slice(1).toLowerCase(); + const caseSensitivePathpath = url.pathname.slice(1); + if (caseSensitivePathpath === encryptKey && encryptKey !== 'doNotModifyDefaultKey') {//fastsub + const params = new URLSearchParams(url.search); + params.set('token', await MD5MD5(host + userID)); + return new Response('redirecting...', { status: 302, headers: { 'Location': `/sub?${params.toString()}` } }); + } else if (visitpath === 'login') {//handleloginPageAndrequest + const cookies = request.headers.get('Cookie') || ''; + const authCookie = cookies.split(';').find(c => c.trim().startsWith('auth='))?.split('=')[1]; + if (authCookie == await MD5MD5(UA + encryptKey + adminPassword)) return new Response('redirecting...', { status: 302, headers: { 'Location': '/admin' } }); + if (request.method === 'POST') { + const formData = await request.text(); + const params = new URLSearchParams(formData); + const inputPassword = params.get('password'); + if (inputPassword === adminPassword) { + + const resp = new Response(JSON.stringify({ success: true }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + resp.headers.set('Set-Cookie', `auth=${await MD5MD5(UA + encryptKey + adminPassword)}; Path=/; Max-Age=86400; HttpOnly`); + return resp; + } + } + return fetch(pagesStaticUrl + '/login'); + } else if (visitpath === 'admin' || visitpath.startsWith('admin/')) {//verifycookieafterrespadminPage + const cookies = request.headers.get('Cookie') || ''; + const authCookie = cookies.split(';').find(c => c.trim().startsWith('auth='))?.split('=')[1]; + + if (!authCookie || authCookie !== await MD5MD5(UA + encryptKey + adminPassword)) return new Response('redirecting...', { status: 302, headers: { 'Location': '/login' } }); + if (visitpath === 'admin/log.json') {// readlogContent + const readlogContent = await env.KV.get('log.json') || '[]'; + return new Response(readlogContent, { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } else if (caseSensitivePathpath === 'admin/getCloudflareUsage') {// queryrequestamt + try { + const Usage_JSON = await getCloudflareUsage(url.searchParams.get('Email'), url.searchParams.get('GlobalAPIKey'), url.searchParams.get('AccountID'), url.searchParams.get('APIToken')); + return new Response(JSON.stringify(Usage_JSON, null, 2), { status: 200, headers: { 'Content-Type': 'application/json' } }); + } catch (err) { + const errorResponse = { msg: 'queryRequestFailed,reason:' + err.message, error: err.message }; + return new Response(JSON.stringify(errorResponse, null, 2), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else if (caseSensitivePathpath === 'admin/getADDAPI') {// verifyoptimalAPI + if (url.searchParams.get('url')) { + const pendingOptimalURL = url.searchParams.get('url'); + try { + new URL(pendingOptimalURL); + const requestOptimalAPIcontent = await requestOptimalAPI([pendingOptimalURL], url.searchParams.get('port') || '443'); + const optimalAPIIP = requestOptimalAPIcontent[0].length > 0 ? requestOptimalAPIcontent[0] : requestOptimalAPIcontent[1]; + return new Response(JSON.stringify({ success: true, data: optimalAPIIP }, null, 2), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (err) { + const errorResponse = { msg: 'verifyAPIFailed,reason:' + err.message, error: err.message }; + return new Response(JSON.stringify(errorResponse, null, 2), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } + return new Response(JSON.stringify({ success: false, data: [] }, null, 2), { status: 403, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } else if (visitpath === 'admin/check') {// SOCKS5proxycheck + let checkProxyResponse; + if (url.searchParams.has('socks5')) { + checkProxyResponse = await SOCKS5availabilityCheck('socks5', url.searchParams.get('socks5')); + } else if (url.searchParams.has('http')) { + checkProxyResponse = await SOCKS5availabilityCheck('http', url.searchParams.get('http')); + } else { + return new Response(JSON.stringify({ error: 'missingProxyParam' }), { status: 400, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + return new Response(JSON.stringify(checkProxyResponse, null, 2), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + + config_JSON = await readconfig_JSON(env, host, userID, env.PATH); + + if (visitpath === 'admin/init') {// resetCfgtoDefault + try { + config_JSON = await readconfig_JSON(env, host, userID, env.PATH, true); + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Init_Config', config_JSON)); + config_JSON.init = 'configResetToDefault'; + return new Response(JSON.stringify(config_JSON, null, 2), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (err) { + const errorResponse = { msg: 'configResetFailed,reason:' + err.message, error: err.message }; + return new Response(JSON.stringify(errorResponse, null, 2), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else if (request.method === 'POST') {// handle KV op(POST request) + if (visitpath === 'admin/config.json') { // saveconfig.jsonconfig + try { + const newConfig = await request.json(); + + if (!newConfig.UUID || !newConfig.HOST) return new Response(JSON.stringify({ error: 'incompleteConfig' }), { status: 400, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + + + await env.KV.put('config.json', JSON.stringify(newConfig, null, 2)); + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Save_Config', config_JSON)); + return new Response(JSON.stringify({ success: true, message: 'configSaved' }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (error) { + console.error('saveConfigFailed:', error); + return new Response(JSON.stringify({ error: 'saveConfigFailed: ' + error.message }), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else if (visitpath === 'admin/cf.json') { // savecf.jsonconfig + try { + const newConfig = await request.json(); + const CF_JSON = { Email: null, GlobalAPIKey: null, AccountID: null, APIToken: null, UsageAPI: null }; + if (!newConfig.init || newConfig.init !== true) { + if (newConfig.Email && newConfig.GlobalAPIKey) { + CF_JSON.Email = newConfig.Email; + CF_JSON.GlobalAPIKey = newConfig.GlobalAPIKey; + } else if (newConfig.AccountID && newConfig.APIToken) { + CF_JSON.AccountID = newConfig.AccountID; + CF_JSON.APIToken = newConfig.APIToken; + } else if (newConfig.UsageAPI) { + CF_JSON.UsageAPI = newConfig.UsageAPI; + } else { + return new Response(JSON.stringify({ error: 'incompleteConfig' }), { status: 400, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } + + + await env.KV.put('cf.json', JSON.stringify(CF_JSON, null, 2)); + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Save_Config', config_JSON)); + return new Response(JSON.stringify({ success: true, message: 'configSaved' }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (error) { + console.error('saveConfigFailed:', error); + return new Response(JSON.stringify({ error: 'saveConfigFailed: ' + error.message }), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else if (visitpath === 'admin/tg.json') { // savetg.jsonconfig + try { + const newConfig = await request.json(); + if (newConfig.init && newConfig.init === true) { + const TG_JSON = { BotToken: null, ChatID: null }; + await env.KV.put('tg.json', JSON.stringify(TG_JSON, null, 2)); + } else { + if (!newConfig.BotToken || !newConfig.ChatID) return new Response(JSON.stringify({ error: 'incompleteConfig' }), { status: 400, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + await env.KV.put('tg.json', JSON.stringify(newConfig, null, 2)); + } + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Save_Config', config_JSON)); + return new Response(JSON.stringify({ success: true, message: 'configSaved' }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (error) { + console.error('saveConfigFailed:', error); + return new Response(JSON.stringify({ error: 'saveConfigFailed: ' + error.message }), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else if (caseSensitivePathpath === 'admin/ADD.txt') { // saveCustomoptimalIP + try { + const customIPs = await request.text(); + await env.KV.put('ADD.txt', customIPs);// saveTo KV + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Save_Custom_IPs', config_JSON)); + return new Response(JSON.stringify({ success: true, message: 'customIPSaved' }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } catch (error) { + console.error('saveCustomIPfailed:', error); + return new Response(JSON.stringify({ error: 'saveCustomIPfailed: ' + error.message }), { status: 500, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + } else return new Response(JSON.stringify({ error: 'unsupportedPOSTPath' }), { status: 404, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } else if (visitpath === 'admin/config.json') {// handle admin/config.json request,returnJSON + return new Response(JSON.stringify(config_JSON, null, 2), { status: 200, headers: { 'Content-Type': 'application/json' } }); + } else if (caseSensitivePathpath === 'admin/ADD.txt') {// handle admin/ADD.txt request,returnlocaloptimalIP + let localoptimalIP = await env.KV.get('ADD.txt') || 'null'; + if (localoptimalIP == 'null') localoptimalIP = (await genrandomIP(request, config_JSON.subGenerator.localIPPool.randomCount, config_JSON.subGenerator.localIPPool.specifiedPort))[1]; + return new Response(localoptimalIP, { status: 200, headers: { 'Content-Type': 'text/plain;charset=utf-8', 'asn': request.cf.asn } }); + } else if (visitpath === 'admin/cf.json') {// CFcfgFile + return new Response(JSON.stringify(request.cf, null, 2), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } }); + } + + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Admin_Login', config_JSON)); + return fetch(pagesStaticUrl + '/admin'); + } else if (visitpath === 'logout' || uuidRegex.test(visitpath)) {//clearcookieredirectToLogin + const resp = new Response('redirecting...', { status: 302, headers: { 'Location': '/login' } }); + resp.headers.set('Set-Cookie', 'auth=; Path=/; Max-Age=0; HttpOnly'); + return resp; + } else if (visitpath === 'sub') {//handlesubrequest + const subTOKEN = await MD5MD5(host + userID); + if (url.searchParams.get('token') === subTOKEN) { + config_JSON = await readconfig_JSON(env, host, userID, env.PATH); + ctx.waitUntil(requestLogRecord(env, request, clientIP, 'Get_SUB', config_JSON)); + const ua = UA.toLowerCase(); + const expire = 4102329600;//2099-12-31 expiretime + const now = Date.now(); + const today = new Date(now); + today.setHours(0, 0, 0, 0); + const UD = Math.floor(((now - today.getTime()) / 86400000) * 24 * 1099511627776 / 2); + let pagesSum = UD, workersSum = UD, total = 24 * 1099511627776; + if (config_JSON.CF.Usage.success) { + pagesSum = config_JSON.CF.Usage.pages; + workersSum = config_JSON.CF.Usage.workers; + total = Number.isFinite(config_JSON.CF.Usage.max) ? (config_JSON.CF.Usage.max / 1000) * 1024 : 1024 * 100; + } + const responseHeaders = { + "content-type": "text/plain; charset=utf-8", + "Profile-Update-Interval": config_JSON.subGenerator.SUBUpdateTime, + "Profile-web-page-url": url.protocol + '//' + url.host + '/admin', + "Subscription-Userinfo": `upload=${pagesSum}; download=${workersSum}; total=${total}; expire=${expire}`, + "Cache-Control": "no-store", + }; + const isSubConverterRequest = url.searchParams.has('b64') || url.searchParams.has('base64') || request.headers.get('subconverter-request') || request.headers.get('subconverter-version') || ua.includes('subconverter') || ua.includes(('CF-Workers-SUB').toLowerCase()); + const subtype = isSubConverterRequest + ? 'mixed' + : url.searchParams.has('target') + ? url.searchParams.get('target') + : url.searchParams.has('clash') || ua.includes('clash') || ua.includes('meta') || ua.includes('mihomo') + ? 'clash' + : url.searchParams.has('sb') || url.searchParams.has('singbox') || ua.includes('singbox') || ua.includes('sing-box') + ? 'singbox' + : url.searchParams.has('surge') || ua.includes('surge') + ? 'surge&ver=4' + : url.searchParams.has('quanx') || ua.includes('quantumult') + ? 'quanx' + : url.searchParams.has('loon') || ua.includes('loon') + ? 'loon' + : 'mixed'; + + if (!ua.includes('mozilla')) responseHeaders["Content-Disposition"] = `attachment; filename*=utf-8''${encodeURIComponent(config_JSON.subGenerator.SUBNAME)}`; + const protocolType = (url.searchParams.has('surge') || ua.includes('surge')) ? 'tro' + 'jan' : config_JSON.protocolType; + let subContent = ''; + if (subtype === 'mixed') { + const nodepath = config_JSON.enable0RTT ? config_JSON.PATH + '?ed=2560' : config_JSON.PATH; + const tlsFragmentparam = config_JSON.tlsFragment == 'Shadowrocket' ? `&fragment=${encodeURIComponent('1,40-60,30-50,tlshello')}` : config_JSON.tlsFragment == 'Happ' ? `&fragment=${encodeURIComponent('3,1,tlshello')}` : ''; + let fullOptimalIP = [], otherNodesLINK = ''; + + if (!url.searchParams.has('sub') && config_JSON.subGenerator.local) { // localgensub + const fullOptimalList = config_JSON.subGenerator.localIPPool.randomIP ? (await genrandomIP(request, config_JSON.subGenerator.localIPPool.randomCount, config_JSON.subGenerator.localIPPool.specifiedPort))[0] : await env.KV.get('ADD.txt') ? await parseToArray(await env.KV.get('ADD.txt')) : (await genrandomIP(request, config_JSON.subGenerator.localIPPool.randomCount, config_JSON.subGenerator.localIPPool.specifiedPort))[0]; + const optimalAPI = [], optimalIP = [], otherNodes = []; + for (const element of fullOptimalList) { + if (element.toLowerCase().startsWith('https://')) optimalAPI.push(element); + else if (element.toLowerCase().includes('://')) { + if (element.includes('#')) { + const addrRemarkSplit = element.split('#'); + otherNodes.push(addrRemarkSplit[0] + '#' + encodeURIComponent(decodeURIComponent(addrRemarkSplit[1]))); + } else otherNodes.push(element); + } else optimalIP.push(element); + } + const requestOptimalAPIcontent = await requestOptimalAPI(optimalAPI); + const mergeOtherNodes = [...new Set(otherNodes.concat(requestOptimalAPIcontent[1]))]; + otherNodesLINK = mergeOtherNodes.length > 0 ? mergeOtherNodes.join('\n') + '\n' : ''; + const optimalAPIIP = requestOptimalAPIcontent[0]; + fullOptimalIP = [...new Set(optimalIP.concat(optimalAPIIP))]; + } else { // subGeneratorer + let subGeneratorerHOST = url.searchParams.get('sub') || config_JSON.subGenerator.SUB; + subGeneratorerHOST = subGeneratorerHOST && !/^https?:\/\//i.test(subGeneratorerHOST) ? `https://${subGeneratorerHOST}` : subGeneratorerHOST; + const subGeneratorerURL = `${subGeneratorerHOST}/sub?host=example.com&uuid=00000000-0000-4000-8000-000000000000`; + try { + const response = await fetch(subGeneratorerURL, { headers: { 'User-Agent': 'v2rayN/edge' + 'tunnel (https://github.com/cmliu/edge' + 'tunnel)' } }); + if (!response.ok) return new Response('subGenError:' + response.statusText, { status: response.status }); + const subGeneratorerReturnsubContent = atob(await response.text()); + const subLineList = subGeneratorerReturnsubContent.includes('\r\n') ? subGeneratorerReturnsubContent.split('\r\n') : subGeneratorerReturnsubContent.split('\n'); + for (const lineContent of subLineList) { + if (!lineContent.trim()) continue; // skipEmpty + if (lineContent.includes('00000000-0000-4000-8000-000000000000') && lineContent.includes('example.com')) { // thisIsoptimalIPline,extract domain:port#remark + const addrMatch = lineContent.match(/:\/\/[^@]+@([^?]+)/); + if (addrMatch) { + let addrPort = addrMatch[1], remark = ''; // domain:port or IP:port + const remarkMatch = lineContent.match(/#(.+)$/); + if (remarkMatch) remark = '#' + decodeURIComponent(remarkMatch[1]); + fullOptimalIP.push(addrPort + remark); + } + } else otherNodesLINK += lineContent + '\n'; + } + } catch (error) { + return new Response('subGenError:' + error.message, { status: 403 }); + } + } + const ECHLINKparam = config_JSON.ECH ? `&ech=${encodeURIComponent('cloudflare-ech.com+' + ECH_DOH)}` : ''; + subContent = otherNodesLINK + fullOptimalIP.map(origAddr => { + + const regex = /^(\[[\da-fA-F:]+\]|[\d.]+|[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*)(?::(\d+))?(?:#(.+))?$/; + const match = origAddr.match(regex); + + let nodeAddr, nodePort = "443", nodeRemark; + + if (match) { + nodeAddr = match[1]; // IPaddrordomain(mayHaveBrackets) + nodePort = match[2] || "443"; // port,def443 + nodeRemark = match[3] || nodeAddr; // remark,defToaddritself + } else { + + console.warn(`[subContent] invalidIPIgnored: ${origAddr}`); + return null; + } + + return `${protocolType}://00000000-0000-4000-8000-000000000000@${nodeAddr}:${nodePort}?security=tls&type=${config_JSON.transport + ECHLINKparam}&host=example.com&fp=${config_JSON.Fingerprint}&sni=example.com&path=${encodeURIComponent(config_JSON.randomPath ? randomPath() + nodepath : nodepath) + tlsFragmentparam}&encryption=none${config_JSON.skipCertVerify ? '&insecure=1&allowInsecure=1' : ''}#${encodeURIComponent(nodeRemark)}`; + }).filter(item => item !== null).join('\n'); + } else { // subConvert + const subConvertURL = `${config_JSON.subConverterConfig.SUBAPI}/sub?target=${subtype}&url=${encodeURIComponent(url.protocol + '//' + url.host + '/sub?target=mixed&token=' + subTOKEN + (url.searchParams.has('sub') && url.searchParams.get('sub') != '' ? `&sub=${url.searchParams.get('sub')}` : ''))}&config=${encodeURIComponent(config_JSON.subConverterConfig.SUBCONFIG)}&emoji=${config_JSON.subConverterConfig.SUBEMOJI}&scv=${config_JSON.skipCertVerify}`; + try { + const response = await fetch(subConvertURL, { headers: { 'User-Agent': 'Subconverter for ' + subtype + ' edge' + 'tunnel(https://github.com/cmliu/edge' + 'tunnel)' } }); + if (response.ok) { + subContent = await response.text(); + if (url.searchParams.has('surge') || ua.includes('surge')) subContent = SurgesubConfigHotfix(subContent, url.protocol + '//' + url.host + '/sub?token=' + subTOKEN + '&surge', config_JSON); + } else return new Response('subConverterError:' + response.statusText, { status: response.status }); + } catch (error) { + return new Response('subConverterError:' + error.message, { status: 403 }); + } + } + + if (!ua.includes('subconverter')) subContent = await batchReplacedomain(subContent.replace(/00000000-0000-4000-8000-000000000000/g, config_JSON.UUID), config_JSON.HOSTS) + + if (subtype === 'mixed' && (!ua.includes('mozilla') || url.searchParams.has('b64') || url.searchParams.has('base64'))) subContent = btoa(subContent); + + if (subtype === 'singbox') { + subContent = SingboxsubConfigHotfix(subContent, config_JSON.UUID, config_JSON.Fingerprint, config_JSON.ECH ? await getECH(host) : null); + responseHeaders["content-type"] = 'application/json; charset=utf-8'; + } else if (subtype === 'clash') { + subContent = ClashsubConfigHotfix(subContent, config_JSON.UUID, config_JSON.ECH, config_JSON.HOSTS); + responseHeaders["content-type"] = 'application/x-yaml; charset=utf-8'; + } + return new Response(subContent, { status: 200, headers: responseHeaders }); + } + } else if (visitpath === 'locations') {//reverseProxylocationslist + const cookies = request.headers.get('Cookie') || ''; + const authCookie = cookies.split(';').find(c => c.trim().startsWith('auth='))?.split('=')[1]; + if (authCookie && authCookie == await MD5MD5(UA + encryptKey + adminPassword)) return fetch(new Request('https://speed.cloudflare.com/locations', { headers: { 'Referer': 'https://speed.cloudflare.com/' } })); + } else if (visitpath === 'robots.txt') return new Response('User-agent: *\nDisallow: /', { status: 200, headers: { 'Content-Type': 'text/plain; charset=UTF-8' } }); + } else if (!envUUID) return fetch(pagesStaticUrl + '/noKV').then(r => { const headers = new Headers(r.headers); headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); headers.set('Pragma', 'no-cache'); headers.set('Expires', '0'); return new Response(r.body, { status: 404, statusText: r.statusText, headers }); }); + } else if (adminPassword) {// wsproxy + await reverseProxyparamget(request); + + const urlPath = new URL(request.url).pathname; + let twoProxyFromPath = ''; + if (urlPath.includes('two_proxy=')) { + const match = urlPath.match(/two_proxy=([^&]+)/); + if (match) { + twoProxyFromPath = decodeURIComponent(decodeURIComponent(match[1])); + } + } + + const twoProxyConfig = twoProxyFromPath || env.TWO_PROXY || env.two_proxy || ''; + return await handleWSrequest(request, userID, twoProxyConfig); + } + + let fakePageURL = env.URL || 'nginx'; + if (fakePageURL && fakePageURL !== 'nginx' && fakePageURL !== '1101') { + fakePageURL = fakePageURL.trim().replace(/\/$/, ''); + if (!fakePageURL.match(/^https?:\/\//i)) fakePageURL = 'https://' + fakePageURL; + if (fakePageURL.toLowerCase().startsWith('http://')) fakePageURL = 'https://' + fakePageURL.substring(7); + try { const u = new URL(fakePageURL); fakePageURL = u.protocol + '//' + u.host; } catch (e) { fakePageURL = 'nginx'; } + } + if (fakePageURL === '1101') return new Response(await html1101(url.host, clientIP), { status: 200, headers: { 'Content-Type': 'text/html; charset=UTF-8' } }); + try { + const reverseProxyURL = new URL(fakePageURL), newHeaders = new Headers(request.headers); + newHeaders.set('Host', reverseProxyURL.host); + newHeaders.set('Referer', reverseProxyURL.origin); + newHeaders.set('Origin', reverseProxyURL.origin); + if (!newHeaders.has('User-Agent') && UA && UA !== 'null') newHeaders.set('User-Agent', UA); + const reverseProxyresp = await fetch(reverseProxyURL.origin + url.pathname + url.search, { method: request.method, headers: newHeaders, body: request.body, cf: request.cf }); + const contenttype = reverseProxyresp.headers.get('content-type') || ''; + + if (/text|javascript|json|xml/.test(contenttype)) { + const respContent = (await reverseProxyresp.text()).replaceAll(reverseProxyURL.host, url.host); + return new Response(respContent, { status: reverseProxyresp.status, headers: { ...Object.fromEntries(reverseProxyresp.headers), 'Cache-Control': 'no-store' } }); + } + return reverseProxyresp; + } catch (error) { } + return new Response(await nginx(), { status: 200, headers: { 'Content-Type': 'text/html; charset=UTF-8' } }); + } +}; + +async function handleWSrequest(request, yourUUID, twoProxy = '') { + const wssPair = new WebSocketPair(); + const [clientSock, serverSock] = Object.values(wssPair); + serverSock.accept(); + let remoteConnWrapper = { socket: null }; + let isDnsQuery = false; + const earlyData = request.headers.get('sec-websocket-protocol') || ''; + const readable = makeReadableStr(serverSock, earlyData); + let checkIfTrojan = null; + + + let twoProxyParsed = null; + if (twoProxy) { + const parts = twoProxy.split(':'); + if (parts.length >= 2) { + twoProxyParsed = { + hostname: parts[0], + port: parseInt(parts[1], 10), + username: parts[2] || '', + password: parts[3] || '' + }; + console.log(`[twoProxy] enabled: ${twoProxyParsed.hostname}:${twoProxyParsed.port}`); + } + } + + readable.pipeTo(new WritableStream({ + async write(chunk) { + if (isDnsQuery) return await forwardataudp(chunk, serverSock, null); + if (remoteConnWrapper.socket) { + const writer = remoteConnWrapper.socket.writable.getWriter(); + await writer.write(chunk); + writer.releaseLock(); + return; + } + + if (checkIfTrojan === null) { + const bytes = new Uint8Array(chunk); + checkIfTrojan = bytes.byteLength >= 58 && bytes[56] === 0x0d && bytes[57] === 0x0a; + } + + if (remoteConnWrapper.socket) { + const writer = remoteConnWrapper.socket.writable.getWriter(); + await writer.write(chunk); + writer.releaseLock(); + return; + } + + if (checkIfTrojan) { + const { port, hostname, rawClientData } = parseTrojanReq(chunk, yourUUID); + if (isSpeedTestSite(hostname)) throw new Error('Speedtest site is blocked'); + await forwardataTCP(hostname, port, rawClientData, serverSock, null, remoteConnWrapper, yourUUID, twoProxyParsed); + } else { + const { port, hostname, rawIndex, version, isUDP } = parseVlessReq(chunk, yourUUID); + if (isSpeedTestSite(hostname)) throw new Error('Speedtest site is blocked'); + if (isUDP) { + if (port === 53) isDnsQuery = true; + else throw new Error('UDP is not supported'); + } + const respHeader = new Uint8Array([version[0], 0]); + const rawData = chunk.slice(rawIndex); + if (isDnsQuery) return forwardataudp(rawData, serverSock, respHeader); + await forwardataTCP(hostname, port, rawData, serverSock, respHeader, remoteConnWrapper, yourUUID, twoProxyParsed); + } + }, + })).catch((err) => { + // console.error('Readable pipe error:', err); + }); + + return new Response(null, { status: 101, webSocket: clientSock }); +} + +function parseTrojanReq(buffer, passwordPlainText) { + const sha224Password = sha224(passwordPlainText); + if (buffer.byteLength < 56) return { hasError: true, message: "invalid data" }; + let crLfIndex = 56; + if (new Uint8Array(buffer.slice(56, 57))[0] !== 0x0d || new Uint8Array(buffer.slice(57, 58))[0] !== 0x0a) return { hasError: true, message: "invalid header format" }; + const password = new TextDecoder().decode(buffer.slice(0, crLfIndex)); + if (password !== sha224Password) return { hasError: true, message: "invalid password" }; + + const socks5DataBuffer = buffer.slice(crLfIndex + 2); + if (socks5DataBuffer.byteLength < 6) return { hasError: true, message: "invalid S5 request data" }; + + const view = new DataView(socks5DataBuffer); + const cmd = view.getUint8(0); + if (cmd !== 1) return { hasError: true, message: "unsupported command, only TCP is allowed" }; + + const atype = view.getUint8(1); + let addressLength = 0; + let addressIndex = 2; + let address = ""; + switch (atype) { + case 1: // IPv4 + addressLength = 4; + address = new Uint8Array(socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)).join("."); + break; + case 3: // Domain + addressLength = new Uint8Array(socks5DataBuffer.slice(addressIndex, addressIndex + 1))[0]; + addressIndex += 1; + address = new TextDecoder().decode(socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)); + break; + case 4: // IPv6 + addressLength = 16; + const dataView = new DataView(socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)); + const ipv6 = []; + for (let i = 0; i < 8; i++) { + ipv6.push(dataView.getUint16(i * 2).toString(16)); + } + address = ipv6.join(":"); + break; + default: + return { hasError: true, message: `invalid addressType is ${atype}` }; + } + + if (!address) { + return { hasError: true, message: `address is empty, addressType is ${atype}` }; + } + + const portIndex = addressIndex + addressLength; + const portBuffer = socks5DataBuffer.slice(portIndex, portIndex + 2); + const portRemote = new DataView(portBuffer).getUint16(0); + + return { + hasError: false, + addressType: atype, + port: portRemote, + hostname: address, + rawClientData: socks5DataBuffer.slice(portIndex + 4) + }; +} + +function parseVlessReq(chunk, token) { + if (chunk.byteLength < 24) return { hasError: true, message: 'Invalid data' }; + const version = new Uint8Array(chunk.slice(0, 1)); + if (formatIdentifier(new Uint8Array(chunk.slice(1, 17))) !== token) return { hasError: true, message: 'Invalid uuid' }; + const optLen = new Uint8Array(chunk.slice(17, 18))[0]; + const cmd = new Uint8Array(chunk.slice(18 + optLen, 19 + optLen))[0]; + let isUDP = false; + if (cmd === 1) { } else if (cmd === 2) { isUDP = true; } else { return { hasError: true, message: 'Invalid command' }; } + const portIdx = 19 + optLen; + const port = new DataView(chunk.slice(portIdx, portIdx + 2)).getUint16(0); + let addrIdx = portIdx + 2, addrLen = 0, addrValIdx = addrIdx + 1, hostname = ''; + const addressType = new Uint8Array(chunk.slice(addrIdx, addrValIdx))[0]; + switch (addressType) { + case 1: + addrLen = 4; + hostname = new Uint8Array(chunk.slice(addrValIdx, addrValIdx + addrLen)).join('.'); + break; + case 2: + addrLen = new Uint8Array(chunk.slice(addrValIdx, addrValIdx + 1))[0]; + addrValIdx += 1; + hostname = new TextDecoder().decode(chunk.slice(addrValIdx, addrValIdx + addrLen)); + break; + case 3: + addrLen = 16; + const ipv6 = []; + const ipv6View = new DataView(chunk.slice(addrValIdx, addrValIdx + addrLen)); + for (let i = 0; i < 8; i++) ipv6.push(ipv6View.getUint16(i * 2).toString(16)); + hostname = ipv6.join(':'); + break; + default: + return { hasError: true, message: `Invalid address type: ${addressType}` }; + } + if (!hostname) return { hasError: true, message: `Invalid address: ${addressType}` }; + return { hasError: false, addressType, port, hostname, isUDP, rawIndex: addrValIdx + addrLen, version }; +} +async function forwardataTCP(host, portNum, rawData, ws, respHeader, remoteConnWrapper, yourUUID, twoProxy = null) { + console.log(`[TCPforward] target: ${host}:${portNum} | proxyIP: ${proxyIP} | twoProxy: ${twoProxy ? twoProxy.hostname + ':' + twoProxy.port : 'no'} | proxyType: ${enableSOCKS5Proxy || 'proxyip'}`); + + async function connectDirect(address, port, data, allreverseProxyarr = null, reverseProxyfallback = true) { + let remoteSock; + if (allreverseProxyarr && allreverseProxyarr.length > 0) { + for (let i = 0; i < allreverseProxyarr.length; i++) { + const reverseProxyarrIndex = (cachedProxyIndex + i) % allreverseProxyarr.length; + const [reverseProxyaddr, reverseProxyport] = allreverseProxyarr[reverseProxyarrIndex]; + try { + console.log(`[proxyConn] connectingTo: ${reverseProxyaddr}:${reverseProxyport} (idx: ${reverseProxyarrIndex})`); + remoteSock = connect({ hostname: reverseProxyaddr, port: reverseProxyport }); + + await Promise.race([ + remoteSock.opened, + new Promise((_, reject) => setTimeout(() => reject(new Error('connTimeout')), 1000)) + ]); + const testWriter = remoteSock.writable.getWriter(); + await testWriter.write(data); + testWriter.releaseLock(); + console.log(`[proxyConn] connectedTo: ${reverseProxyaddr}:${reverseProxyport}`); + cachedProxyIndex = reverseProxyarrIndex; + return remoteSock; + } catch (err) { + console.log(`[proxyConn] connFailed: ${reverseProxyaddr}:${reverseProxyport}, err: ${err.message}`); + try { remoteSock?.close?.(); } catch (e) { } + continue; + } + } + } + + if (reverseProxyfallback) { + remoteSock = connect({ hostname: address, port: port }); + const writer = remoteSock.writable.getWriter(); + await writer.write(data); + writer.releaseLock(); + return remoteSock; + } else { + closeSocketQuietly(ws); + throw new Error('[proxyConn] allProxyFailed,andNotenableProxyFallback,connTerminated。'); + } + } + + async function connecttoPry() { + let newSocket; + if (enableSOCKS5Proxy === 'socks5') { + console.log(`[SOCKS5proxy] proxyTo: ${host}:${portNum}`); + newSocket = await socks5Connect(host, portNum, rawData); + } else if (enableSOCKS5Proxy === 'http' || enableSOCKS5Proxy === 'https') { + console.log(`[HTTPproxy] proxyTo: ${host}:${portNum}`); + newSocket = await httpConnect(host, portNum, rawData); + } else { + console.log(`[proxyConn] proxyTo: ${host}:${portNum}`); + const allreverseProxyarr = await parseAddrPort(proxyIP, host, yourUUID); + newSocket = await connectDirect(atob('UFJPWFlJUC50cDEuMDkwMjI3Lnh5eg=='), 1, rawData, allreverseProxyarr, enableProxyFallback); + } + remoteConnWrapper.socket = newSocket; + newSocket.closed.catch(() => { }).finally(() => closeSocketQuietly(ws)); + connectStreams(newSocket, ws, respHeader, null); + } + + + async function connectViaTwoProxy() { + if (!twoProxy) throw new Error('twoProxyNotConfigured'); + console.log(`[twoProxy] via ${twoProxy.hostname}:${twoProxy.port} connTo ${host}:${portNum}`); + + const socket = connect({ hostname: twoProxy.hostname, port: twoProxy.port }); + const writer = socket.writable.getWriter(); + const reader = socket.readable.getReader(); + + try { + + const auth = twoProxy.username && twoProxy.password + ? `Proxy-Authorization: Basic ${btoa(`${twoProxy.username}:${twoProxy.password}`)}\r\n` + : ''; + const connectRequest = `CONNECT ${host}:${portNum} HTTP/1.1\r\nHost: ${host}:${portNum}\r\n${auth}User-Agent: Mozilla/5.0\r\nConnection: keep-alive\r\n\r\n`; + + await writer.write(new TextEncoder().encode(connectRequest)); + + + let responseBuffer = new Uint8Array(0); + let headerEndIndex = -1; + let bytesRead = 0; + + while (headerEndIndex === -1 && bytesRead < 8192) { + const { done, value } = await reader.read(); + if (done) throw new Error('proxyClosed'); + responseBuffer = new Uint8Array([...responseBuffer, ...value]); + bytesRead = responseBuffer.length; + + + for (let i = 0; i < responseBuffer.length - 3; i++) { + if (responseBuffer[i] === 0x0d && responseBuffer[i + 1] === 0x0a && + responseBuffer[i + 2] === 0x0d && responseBuffer[i + 3] === 0x0a) { + headerEndIndex = i + 4; + break; + } + } + } + + if (headerEndIndex === -1) throw new Error('invalidProxyResp'); + + const responseText = new TextDecoder().decode(responseBuffer.slice(0, headerEndIndex)); + const statusMatch = responseText.match(/HTTP\/\d\.\d\s+(\d+)/); + const statusCode = statusMatch ? parseInt(statusMatch[1]) : 0; + + if (statusCode < 200 || statusCode >= 300) { + throw new Error(`proxyConnFailed: HTTP ${statusCode}`); + } + + console.log(`[twoProxy] tunnelEstablished: ${host}:${portNum}`); + + + await writer.write(rawData); + writer.releaseLock(); + reader.releaseLock(); + + return socket; + } catch (error) { + try { writer.releaseLock(); } catch (e) { } + try { reader.releaseLock(); } catch (e) { } + try { socket.close(); } catch (e) { } + throw error; + } + } + + const verifysocks5Whitelist = (addr) => socks5Whitelist.some(p => new RegExp(`^${p.replace(/\*/g, '.*')}$`, 'i').test(addr)); + + + if (twoProxy) { + console.log(`[TCPforward] usingTwoProxy`); + try { + const proxySocket = await connectViaTwoProxy(); + remoteConnWrapper.socket = proxySocket; + proxySocket.closed.catch(() => { }).finally(() => closeSocketQuietly(ws)); + connectStreams(proxySocket, ws, respHeader, null); + } catch (err) { + console.log(`[twoProxy] connFailed: ${err.message}, fallbackToDefault`); + + try { + await connecttoPry(); + } catch (err2) { + throw err2; + } + } + } else if (enableSOCKS5Proxy && (enableGlobalSOCKS5 || verifysocks5Whitelist(host))) { + console.log(`[TCPforward] enableGlobalProxy`); + try { + await connecttoPry(); + } catch (err) { + throw err; + } + } else { + try { + console.log(`[TCPforward] directConnTo: ${host}:${portNum}`); + const initialSocket = await connectDirect(host, portNum, rawData); + remoteConnWrapper.socket = initialSocket; + connectStreams(initialSocket, ws, respHeader, connecttoPry); + } catch (err) { + await connecttoPry(); + } + } +} + +async function forwardataudp(udpChunk, webSocket, respHeader) { + try { + const tcpSocket = connect({ hostname: '8.8.4.4', port: 53 }); + let vlessHeader = respHeader; + const writer = tcpSocket.writable.getWriter(); + await writer.write(udpChunk); + writer.releaseLock(); + await tcpSocket.readable.pipeTo(new WritableStream({ + async write(chunk) { + if (webSocket.readyState === WebSocket.OPEN) { + if (vlessHeader) { + const response = new Uint8Array(vlessHeader.length + chunk.byteLength); + response.set(vlessHeader, 0); + response.set(chunk, vlessHeader.length); + webSocket.send(response.buffer); + vlessHeader = null; + } else { + webSocket.send(chunk); + } + } + }, + })); + } catch (error) { + // console.error('UDP forward error:', error); + } +} + +function closeSocketQuietly(socket) { + try { + if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CLOSING) { + socket.close(); + } + } catch (error) { } +} + +function formatIdentifier(arr, offset = 0) { + const hex = [...arr.slice(offset, offset + 16)].map(b => b.toString(16).padStart(2, '0')).join(''); + return `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20)}`; +} +async function connectStreams(remoteSocket, webSocket, headerData, retryFunc) { + let header = headerData, hasData = false; + await remoteSocket.readable.pipeTo( + new WritableStream({ + async write(chunk, controller) { + hasData = true; + if (webSocket.readyState !== WebSocket.OPEN) controller.error('ws.readyState is not open'); + if (header) { + const response = new Uint8Array(header.length + chunk.byteLength); + response.set(header, 0); + response.set(chunk, header.length); + webSocket.send(response.buffer); + header = null; + } else { + webSocket.send(chunk); + } + }, + abort() { }, + }) + ).catch((err) => { + closeSocketQuietly(webSocket); + }); + if (!hasData && retryFunc) { + await retryFunc(); + } +} + +function makeReadableStr(socket, earlyDataHeader) { + let cancelled = false; + return new ReadableStream({ + start(controller) { + socket.addEventListener('message', (event) => { + if (!cancelled) controller.enqueue(event.data); + }); + socket.addEventListener('close', () => { + if (!cancelled) { + closeSocketQuietly(socket); + controller.close(); + } + }); + socket.addEventListener('error', (err) => controller.error(err)); + const { earlyData, error } = base64ToArray(earlyDataHeader); + if (error) controller.error(error); + else if (earlyData) controller.enqueue(earlyData); + }, + cancel() { + cancelled = true; + closeSocketQuietly(socket); + } + }); +} + +function isSpeedTestSite(hostname) { + const speedTestDomains = [atob('c3BlZWQuY2xvdWRmbGFyZS5jb20=')]; + if (speedTestDomains.includes(hostname)) { + return true; + } + + for (const domain of speedTestDomains) { + if (hostname.endsWith('.' + domain) || hostname === domain) { + return true; + } + } + return false; +} + +function base64ToArray(b64Str) { + if (!b64Str) return { error: null }; + try { + const binaryString = atob(b64Str.replace(/-/g, '+').replace(/_/g, '/')); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return { earlyData: bytes.buffer, error: null }; + } catch (error) { + return { error }; + } +} + +async function socks5Connect(targetHost, targetPort, initialData) { + const { username, password, hostname, port } = parsedSocks5Address; + const socket = connect({ hostname, port }), writer = socket.writable.getWriter(), reader = socket.readable.getReader(); + try { + const authMethods = username && password ? new Uint8Array([0x05, 0x02, 0x00, 0x02]) : new Uint8Array([0x05, 0x01, 0x00]); + await writer.write(authMethods); + let response = await reader.read(); + if (response.done || response.value.byteLength < 2) throw new Error('S5 method selection failed'); + + const selectedMethod = new Uint8Array(response.value)[1]; + if (selectedMethod === 0x02) { + if (!username || !password) throw new Error('S5 requires authentication'); + const userBytes = new TextEncoder().encode(username), passBytes = new TextEncoder().encode(password); + const authPacket = new Uint8Array([0x01, userBytes.length, ...userBytes, passBytes.length, ...passBytes]); + await writer.write(authPacket); + response = await reader.read(); + if (response.done || new Uint8Array(response.value)[1] !== 0x00) throw new Error('S5 authentication failed'); + } else if (selectedMethod !== 0x00) throw new Error(`S5 unsupported auth method: ${selectedMethod}`); + + const hostBytes = new TextEncoder().encode(targetHost); + const connectPacket = new Uint8Array([0x05, 0x01, 0x00, 0x03, hostBytes.length, ...hostBytes, targetPort >> 8, targetPort & 0xff]); + await writer.write(connectPacket); + response = await reader.read(); + if (response.done || new Uint8Array(response.value)[1] !== 0x00) throw new Error('S5 connection failed'); + + await writer.write(initialData); + writer.releaseLock(); reader.releaseLock(); + return socket; + } catch (error) { + try { writer.releaseLock(); } catch (e) { } + try { reader.releaseLock(); } catch (e) { } + try { socket.close(); } catch (e) { } + throw error; + } +} + +async function httpConnect(targetHost, targetPort, initialData) { + const { username, password, hostname, port } = parsedSocks5Address; + const socket = connect({ hostname, port }), writer = socket.writable.getWriter(), reader = socket.readable.getReader(); + try { + const auth = username && password ? `Proxy-Authorization: Basic ${btoa(`${username}:${password}`)}\r\n` : ''; + const request = `CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\nHost: ${targetHost}:${targetPort}\r\n${auth}User-Agent: Mozilla/5.0\r\nConnection: keep-alive\r\n\r\n`; + await writer.write(new TextEncoder().encode(request)); + + let responseBuffer = new Uint8Array(0), headerEndIndex = -1, bytesRead = 0; + while (headerEndIndex === -1 && bytesRead < 8192) { + const { done, value } = await reader.read(); + if (done) throw new Error('Connection closed before receiving HTTP response'); + responseBuffer = new Uint8Array([...responseBuffer, ...value]); + bytesRead = responseBuffer.length; + const crlfcrlf = responseBuffer.findIndex((_, i) => i < responseBuffer.length - 3 && responseBuffer[i] === 0x0d && responseBuffer[i + 1] === 0x0a && responseBuffer[i + 2] === 0x0d && responseBuffer[i + 3] === 0x0a); + if (crlfcrlf !== -1) headerEndIndex = crlfcrlf + 4; + } + + if (headerEndIndex === -1) throw new Error('Invalid HTTP response'); + const statusCode = parseInt(new TextDecoder().decode(responseBuffer.slice(0, headerEndIndex)).split('\r\n')[0].match(/HTTP\/\d\.\d\s+(\d+)/)[1]); + if (statusCode < 200 || statusCode >= 300) throw new Error(`Connection failed: HTTP ${statusCode}`); + + await writer.write(initialData); + writer.releaseLock(); reader.releaseLock(); + return socket; + } catch (error) { + try { writer.releaseLock(); } catch (e) { } + try { reader.releaseLock(); } catch (e) { } + try { socket.close(); } catch (e) { } + throw error; + } +} + +function ClashsubConfigHotfix(Clash_rawsubContent, uuid = null, ECHenable = false, HOSTS = []) { + let clash_yaml = Clash_rawsubContent.replace(/mode:\s*Rule\b/g, 'mode: rule'); + + + const baseDnsBlock = `dns: + enable: true + default-nameserver: + - 223.5.5.5 + - 119.29.29.29 + - 114.114.114.114 + use-hosts: true + nameserver: + - https://sm2.doh.pub/dns-query + - https://dns.alidns.com/dns-query + fallback: + - 8.8.4.4 + - 101.101.101.101 + - 208.67.220.220 + fallback-filter: + geoip: true + domain: [+.google.com, +.facebook.com, +.youtube.com] + ipcidr: + - 240.0.0.0/4 + - 0.0.0.0/32 + geoip-code: CN +`; + + + const hasDns = /^dns:\s*(?:\n|$)/m.test(clash_yaml); + + + if (!hasDns) { + clash_yaml = baseDnsBlock + clash_yaml; + } + + + if (ECHenable && HOSTS.length > 0) { + + const hostsEntries = HOSTS.map(host => ` "${host}":\n - tls://8.8.8.8\n - https://doh.cmliussss.com/CMLiussss\n - ${ECH_DOH}`).join('\n'); + + + const hasNameserverPolicy = /^\s{2}nameserver-policy:\s*(?:\n|$)/m.test(clash_yaml); + + if (hasNameserverPolicy) { + + clash_yaml = clash_yaml.replace( + /^(\s{2}nameserver-policy:\s*\n)/m, + `$1${hostsEntries}\n` + ); + } else { + + const lines = clash_yaml.split('\n'); + let dnsBlockEndIndex = -1; + let inDnsBlock = false; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (/^dns:\s*$/.test(line)) { + inDnsBlock = true; + continue; + } + if (inDnsBlock) { + + if (/^[a-zA-Z]/.test(line)) { + dnsBlockEndIndex = i; + break; + } + } + } + + + const nameserverPolicyBlock = ` nameserver-policy:\n${hostsEntries}`; + if (dnsBlockEndIndex !== -1) { + lines.splice(dnsBlockEndIndex, 0, nameserverPolicyBlock); + } else { + + lines.push(nameserverPolicyBlock); + } + clash_yaml = lines.join('\n'); + } + } + + + if (!uuid || !ECHenable) return clash_yaml; + + + const lines = clash_yaml.split('\n'); + const processedLines = []; + let i = 0; + + while (i < lines.length) { + const line = lines[i]; + const trimmedLine = line.trim(); + + + if (trimmedLine.startsWith('- {') && (trimmedLine.includes('uuid:') || trimmedLine.includes('password:'))) { + let fullNode = line; + let braceCount = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length; + + + while (braceCount > 0 && i + 1 < lines.length) { + i++; + fullNode += '\n' + lines[i]; + braceCount += (lines[i].match(/\{/g) || []).length - (lines[i].match(/\}/g) || []).length; + } + + + const typeMatch = fullNode.match(/type:\s*(\w+)/); + const proxyType = typeMatch ? typeMatch[1] : 'vless'; + + + let credentialField = 'uuid'; + if (proxyType === 'trojan') { + credentialField = 'password'; + } + + + const credentialPattern = new RegExp(`${credentialField}:\\s*([^,}\\n]+)`); + const credentialMatch = fullNode.match(credentialPattern); + + if (credentialMatch && credentialMatch[1].trim() === uuid.trim()) { + + fullNode = fullNode.replace(/\}(\s*)$/, `, ech-opts: {enable: true}}$1`); + } + + processedLines.push(fullNode); + i++; + } + + else if (trimmedLine.startsWith('- name:')) { + + let nodeLines = [line]; + let baseIndent = line.search(/\S/); + let topLevelIndent = baseIndent + 2; // topLevelIndent + i++; + + + while (i < lines.length) { + const nextLine = lines[i]; + const nextTrimmed = nextLine.trim(); + + + if (!nextTrimmed) { + nodeLines.push(nextLine); + i++; + break; + } + + const nextIndent = nextLine.search(/\S/); + + + if (nextIndent <= baseIndent && nextTrimmed.startsWith('- ')) { + break; + } + + + if (nextIndent < baseIndent && nextTrimmed) { + break; + } + + nodeLines.push(nextLine); + i++; + } + + + const nodeText = nodeLines.join('\n'); + const typeMatch = nodeText.match(/type:\s*(\w+)/); + const proxyType = typeMatch ? typeMatch[1] : 'vless'; + + + let credentialField = 'uuid'; + if (proxyType === 'trojan') { + credentialField = 'password'; + } + + + const credentialPattern = new RegExp(`${credentialField}:\\s*([^\\n]+)`); + const credentialMatch = nodeText.match(credentialPattern); + + if (credentialMatch && credentialMatch[1].trim() === uuid.trim()) { + + let insertIndex = -1; + + for (let j = nodeLines.length - 1; j >= 0; j--) { + + if (nodeLines[j].trim()) { + insertIndex = j; + break; + } + } + + if (insertIndex >= 0) { + const indent = ' '.repeat(topLevelIndent); + + nodeLines.splice(insertIndex + 1, 0, + `${indent}ech-opts:`, + `${indent} enable: true` + ); + } + } + + processedLines.push(...nodeLines); + } else { + processedLines.push(line); + i++; + } + } + + return processedLines.join('\n'); +} + +function SingboxsubConfigHotfix(sb_json_text, uuid = null, fingerprint = "chrome", ech_config = null) { + try { + let config = JSON.parse(sb_json_text); + + + if (Array.isArray(config.inbounds)) { + config.inbounds.forEach(inbound => { + if (inbound.type === 'tun') { + const addresses = []; + if (inbound.inet4_address) addresses.push(inbound.inet4_address); + if (inbound.inet6_address) addresses.push(inbound.inet6_address); + if (addresses.length > 0) { + inbound.address = addresses; + delete inbound.inet4_address; + delete inbound.inet6_address; + } + + const route_addresses = []; + if (Array.isArray(inbound.inet4_route_address)) route_addresses.push(...inbound.inet4_route_address); + if (Array.isArray(inbound.inet6_route_address)) route_addresses.push(...inbound.inet6_route_address); + if (route_addresses.length > 0) { + inbound.route_address = route_addresses; + delete inbound.inet4_route_address; + delete inbound.inet6_route_address; + } + + const route_exclude_addresses = []; + if (Array.isArray(inbound.inet4_route_exclude_address)) route_exclude_addresses.push(...inbound.inet4_route_exclude_address); + if (Array.isArray(inbound.inet6_route_exclude_address)) route_exclude_addresses.push(...inbound.inet6_route_exclude_address); + if (route_exclude_addresses.length > 0) { + inbound.route_exclude_address = route_exclude_addresses; + delete inbound.inet4_route_exclude_address; + delete inbound.inet6_route_exclude_address; + } + } + }); + } + + + const ruleSetsDefinitions = new Map(); + const processRules = (rules, isDns = false) => { + if (!Array.isArray(rules)) return; + rules.forEach(rule => { + if (rule.geosite) { + const geositeList = Array.isArray(rule.geosite) ? rule.geosite : [rule.geosite]; + rule.rule_set = geositeList.map(name => { + const tag = `geosite-${name}`; + if (!ruleSetsDefinitions.has(tag)) { + ruleSetsDefinitions.set(tag, { + tag: tag, + type: "remote", + format: "binary", + url: `https://gh.090227.xyz/https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-${name}.srs`, + download_detour: "DIRECT" + }); + } + return tag; + }); + delete rule.geosite; + } + if (rule.geoip) { + const geoipList = Array.isArray(rule.geoip) ? rule.geoip : [rule.geoip]; + rule.rule_set = rule.rule_set || []; + geoipList.forEach(name => { + const tag = `geoip-${name}`; + if (!ruleSetsDefinitions.has(tag)) { + ruleSetsDefinitions.set(tag, { + tag: tag, + type: "remote", + format: "binary", + url: `https://gh.090227.xyz/https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-${name}.srs`, + download_detour: "DIRECT" + }); + } + rule.rule_set.push(tag); + }); + delete rule.geoip; + } + const targetField = isDns ? 'server' : 'outbound'; + const actionValue = String(rule[targetField]).toUpperCase(); + if (actionValue === 'REJECT' || actionValue === 'BLOCK') { + rule.action = 'reject'; + rule.method = 'drop'; // forceModern + delete rule[targetField]; + } + }); + }; + + if (config.dns && config.dns.rules) processRules(config.dns.rules, true); + if (config.route && config.route.rules) processRules(config.route.rules, false); + + if (ruleSetsDefinitions.size > 0) { + if (!config.route) config.route = {}; + config.route.rule_set = Array.from(ruleSetsDefinitions.values()); + } + + + if (!config.outbounds) config.outbounds = []; + + + config.outbounds = config.outbounds.filter(o => { + if (o.tag === 'REJECT' || o.tag === 'block') { + return false; // remove,becauseChanged action: reject + } + return true; + }); + + const existingOutboundTags = new Set(config.outbounds.map(o => o.tag)); + + if (!existingOutboundTags.has('DIRECT')) { + config.outbounds.push({ "type": "direct", "tag": "DIRECT" }); + existingOutboundTags.add('DIRECT'); + } + + if (config.dns && config.dns.servers) { + const dnsServerTags = new Set(config.dns.servers.map(s => s.tag)); + if (config.dns.rules) { + config.dns.rules.forEach(rule => { + if (rule.server && !dnsServerTags.has(rule.server)) { + if (rule.server === 'dns_block' && dnsServerTags.has('block')) { + rule.server = 'block'; + } else if (rule.server.toLowerCase().includes('block') && !dnsServerTags.has(rule.server)) { + config.dns.servers.push({ "tag": rule.server, "address": "rcode://success" }); + dnsServerTags.add(rule.server); + } + } + }); + } + } + + config.outbounds.forEach(outbound => { + if (outbound.type === 'selector' || outbound.type === 'urltest') { + if (Array.isArray(outbound.outbounds)) { + + outbound.outbounds = outbound.outbounds.filter(tag => { + const upperTag = tag.toUpperCase(); + return existingOutboundTags.has(tag) && upperTag !== 'REJECT' && upperTag !== 'BLOCK'; + }); + if (outbound.outbounds.length === 0) outbound.outbounds.push("DIRECT"); + } + } + }); + + + if (uuid) { + config.outbounds.forEach(outbound => { + + if ((outbound.uuid && outbound.uuid === uuid) || (outbound.password && outbound.password === uuid)) { + + if (!outbound.tls) { + outbound.tls = { enabled: true }; + } + + + if (fingerprint) { + outbound.tls.utls = { + enabled: true, + fingerprint: fingerprint + }; + } + + + if (ech_config) { + outbound.tls.ech = { + enabled: true, + config: `-----BEGIN ECH CONFIGS-----\n${ech_config}\n-----END ECH CONFIGS-----` + }; + } + } + }); + } + + return JSON.stringify(config, null, 2); + } catch (e) { + console.error("singboxPatchFailed:", e); + return JSON.stringify(JSON.parse(sb_json_text), null, 2); + } +} + +function SurgesubConfigHotfix(content, url, config_JSON) { + const eachLine = content.includes('\r\n') ? content.split('\r\n') : content.split('\n'); + + let output = ""; + const realSurgePath = config_JSON.enable0RTT ? config_JSON.PATH + '?ed=2560' : config_JSON.PATH; + for (let x of eachLine) { + if (x.includes('= tro' + 'jan,') && !x.includes('ws=true') && !x.includes('ws-path=')) { + const host = x.split("sni=")[1].split(",")[0]; + const backup = `sni=${host}, skip-cert-verify=${config_JSON.skipCertVerify}`; + const correct = `sni=${host}, skip-cert-verify=${config_JSON.skipCertVerify}, ws=true, ws-path=${realSurgePath}, ws-headers=Host:"${host}"`; + output += x.replace(new RegExp(backup, 'g'), correct).replace("[", "").replace("]", "") + '\n'; + } else { + output += x + '\n'; + } + } + + output = `#!MANAGED-CONFIG ${url} interval=${config_JSON.subGenerator.SUBUpdateTime * 60 * 60} strict=false` + output.substring(output.indexOf('\n')); + return output; +} + +async function requestLogRecord(env, request, clientIP, requesttype = "Get_SUB", config_JSON) { + const KVcapLimit = 4;//MB + try { + const currtime = new Date(); + const logContent = { TYPE: requesttype, IP: clientIP, ASN: `AS${request.cf.asn || '0'} ${request.cf.asOrganization || 'Unknown'}`, CC: `${request.cf.country || 'N/A'} ${request.cf.city || 'N/A'}`, URL: request.url, UA: request.headers.get('User-Agent') || 'Unknown', TIME: currtime.getTime() }; + let logArr = []; + const existLog = await env.KV.get('log.json'); + if (existLog) { + try { + logArr = JSON.parse(existLog); + if (!Array.isArray(logArr)) { logArr = [logContent]; } + else if (requesttype !== "Get_SUB") { + const thirtyMinAgotimestamp = currtime.getTime() - 30 * 60 * 1000; + if (logArr.some(log => log.TYPE !== "Get_SUB" && log.IP === clientIP && log.URL === request.url && log.UA === (request.headers.get('User-Agent') || 'Unknown') && log.TIME >= thirtyMinAgotimestamp)) return; + logArr.push(logContent); + while (JSON.stringify(logArr, null, 2).length > KVcapLimit * 1024 * 1024 && logArr.length > 0) logArr.shift(); + } else { + logArr.push(logContent); + while (JSON.stringify(logArr, null, 2).length > KVcapLimit * 1024 * 1024 && logArr.length > 0) logArr.shift(); + } + if (config_JSON.TG.enable) { + try { + const TG_TXT = await env.KV.get('tg.json'); + const TG_JSON = JSON.parse(TG_TXT); + await sendMessage(TG_JSON.BotToken, TG_JSON.ChatID, logContent, config_JSON); + } catch (error) { console.error(`readtg.jsonerror: ${error.message}`) } + } + } catch (e) { logArr = [logContent]; } + } else { logArr = [logContent]; } + await env.KV.put('log.json', JSON.stringify(logArr, null, 2)); + } catch (error) { console.error(`logFailed: ${error.message}`); } +} + +async function sendMessage(BotToken, ChatID, logContent, config_JSON) { + if (!BotToken || !ChatID) return; + + try { + const requesttime = new Date(logContent.TIME).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }); + const requestURL = new URL(logContent.URL); + const msg = `#${config_JSON.subGenerator.SUBNAME} logNotify\n\n` + + `📌 type:#${logContent.TYPE}\n` + + `🌐 IP:${logContent.IP}\n` + + `📍 location:${logContent.CC}\n` + + `🏢 ASN:${logContent.ASN}\n` + + `🔗 domain:${requestURL.host}\n` + + `🔍 path:${requestURL.pathname + requestURL.search}\n` + + `🤖 UA:${logContent.UA}\n` + + `📅 time:${requesttime}\n` + + `${config_JSON.CF.Usage.success ? `📊 requestUsage:${config_JSON.CF.Usage.total}/100000 ${((config_JSON.CF.Usage.total / 100000) * 100).toFixed(2)}%\n` : ''}`; + + const url = `https://api.telegram.org/bot${BotToken}/sendMessage?chat_id=${ChatID}&parse_mode=HTML&text=${encodeURIComponent(msg)}`; + return fetch(url, { + method: 'GET', + headers: { + 'Accept': 'text/html,application/xhtml+xml,application/xml;', + 'Accept-Encoding': 'gzip, deflate, br', + 'User-Agent': logContent.UA || 'Unknown', + } + }); + } catch (error) { console.error('Error sending message:', error) } +} + +function maskSensitive(txt, prefixLen = 3, suffixLen = 2) { + if (!txt || typeof txt !== 'string') return txt; + if (txt.length <= prefixLen + suffixLen) return txt; // likeifTooShort,directReturn + + const prefix = txt.slice(0, prefixLen); + const suffix = txt.slice(-suffixLen); + const starCount = txt.length - prefixLen - suffixLen; + + return `${prefix}${'*'.repeat(starCount)}${suffix}`; +} + +async function MD5MD5(txt) { + const encoder = new TextEncoder(); + + const hash1 = await crypto.subtle.digest('MD5', encoder.encode(txt)); + const hash1Arr = Array.from(new Uint8Array(hash1)); + const hex1 = hash1Arr.map(byte => byte.toString(16).padStart(2, '0')).join(''); + + const hash2 = await crypto.subtle.digest('MD5', encoder.encode(hex1.slice(7, 27))); + const hash2Arr = Array.from(new Uint8Array(hash2)); + const hex2 = hash2Arr.map(byte => byte.toString(16).padStart(2, '0')).join(''); + + return hex2.toLowerCase(); +} + +function randomPath() { + const commonpathdir = ["about", "account", "acg", "act", "activity", "ad", "ads", "ajax", "album", "albums", "anime", "api", "app", "apps", "archive", "archives", "article", "articles", "ask", "auth", "avatar", "bbs", "bd", "blog", "blogs", "book", "books", "bt", "buy", "cart", "category", "categories", "cb", "channel", "channels", "chat", "china", "city", "class", "classify", "clip", "clips", "club", "cn", "code", "collect", "collection", "comic", "comics", "community", "company", "config", "contact", "content", "course", "courses", "cp", "data", "detail", "details", "dh", "directory", "discount", "discuss", "dl", "dload", "doc", "docs", "document", "documents", "doujin", "download", "downloads", "drama", "edu", "en", "ep", "episode", "episodes", "event", "events", "f", "faq", "favorite", "favourites", "favs", "feedback", "file", "files", "film", "films", "forum", "forums", "friend", "friends", "game", "games", "gif", "go", "go.html", "go.php", "group", "groups", "help", "home", "hot", "htm", "html", "image", "images", "img", "index", "info", "intro", "item", "items", "ja", "jp", "jump", "jump.html", "jump.php", "jumping", "knowledge", "lang", "lesson", "lessons", "lib", "library", "link", "links", "list", "live", "lives", "m", "mag", "magnet", "mall", "manhua", "map", "member", "members", "message", "messages", "mobile", "movie", "movies", "music", "my", "new", "news", "note", "novel", "novels", "online", "order", "out", "out.html", "out.php", "outbound", "p", "page", "pages", "pay", "payment", "pdf", "photo", "photos", "pic", "pics", "picture", "pictures", "play", "player", "playlist", "post", "posts", "product", "products", "program", "programs", "project", "qa", "question", "rank", "ranking", "read", "readme", "redirect", "redirect.html", "redirect.php", "reg", "register", "res", "resource", "retrieve", "sale", "search", "season", "seasons", "section", "seller", "series", "service", "services", "setting", "settings", "share", "shop", "show", "shows", "site", "soft", "sort", "source", "special", "star", "stars", "static", "stock", "store", "stream", "streaming", "streams", "student", "study", "tag", "tags", "task", "teacher", "team", "tech", "temp", "test", "thread", "tool", "tools", "topic", "topics", "torrent", "trade", "travel", "tv", "txt", "type", "u", "upload", "uploads", "url", "urls", "user", "users", "v", "version", "video", "videos", "view", "vip", "vod", "watch", "web", "wenku", "wiki", "work", "www", "zh", "zh-cn", "zh-tw", "zip"]; + const randNum = Math.floor(Math.random() * 3 + 1); + const randomPath = commonpathdir.sort(() => 0.5 - Math.random()).slice(0, randNum).join('/'); + return `/${randomPath}`; +} + +function randReplaceWildcard(h) { + if (!h?.includes('*')) return h; + const charset = 'abcdefghijklmnopqrstuvwxyz0123456789'; + return h.replace(/\*/g, () => { + let s = ''; + for (let i = 0; i < Math.floor(Math.random() * 14) + 3; i++) + s += charset[Math.floor(Math.random() * 36)]; + return s; + }); +} + +function batchReplacedomain(content, hosts, groupCnt = 2) { + const shuffledArr = [...hosts].sort(() => Math.random() - 0.5); + let count = 0, currentRandomHost = null; + return content.replace(/example\.com/g, () => { + if (count % groupCnt === 0) currentRandomHost = randReplaceWildcard(shuffledArr[Math.floor(count / groupCnt) % shuffledArr.length]); + count++; + return currentRandomHost; + }); +} + +async function getECH(host) { + try { + const res = await fetch(`https://1.1.1.1/dns-query?name=${encodeURIComponent(host)}&type=65`, { headers: { 'accept': 'application/dns-json' } }); + const data = await res.json(); + if (!data.Answer?.length) return ''; + for (let ans of data.Answer) { + if (ans.type !== 65 || !ans.data) continue; + const match = ans.data.match(/ech=([^\s]+)/); + if (match) return match[1].replace(/"/g, ''); + if (ans.data.startsWith('\\#')) { + const hex = ans.data.split(' ').slice(2).join(''); + const bytes = new Uint8Array(hex.match(/.{1,2}/g).map(b => parseInt(b, 16))); + let offset = 2; + while (offset < bytes.length && bytes[offset++] !== 0) + offset += bytes[offset - 1]; + + while (offset + 4 <= bytes.length) { + const key = (bytes[offset] << 8) | bytes[offset + 1]; + const len = (bytes[offset + 2] << 8) | bytes[offset + 3]; + offset += 4; + + if (key === 5) return btoa(String.fromCharCode(...bytes.slice(offset, offset + len))); + offset += len; + } + } + } + return ''; + } catch { + return ''; + } +} + +async function readconfig_JSON(env, hostname, userID, path, resetCfg = false) { + + const host = hostname; + const initStarttime = performance.now(); + const defCfgJSON = { + TIME: new Date().toISOString(), + HOST: host, + HOSTS: [hostname], + UUID: userID, + protocolType: "v" + "le" + "ss", + transport: "ws", + skipCertVerify: true, + enable0RTT: false, + tlsFragment: null, + randomPath: false, + ECH: false, + Fingerprint: "chrome", + subGenerator: { + local: true, // true: basedOnlocaloptimaladdr false: subGeneratorer + localIPPool: { + randomIP: true, // when randomIP istrueeffective,enablerandomIPcnt,nothenUseKVinADD.txt + randomCount: 16, + specifiedPort: -1, + }, + SUB: null, + SUBNAME: "edge" + "tunnel", + SUBUpdateTime: 3, // subupdatetime(hour) + TOKEN: await MD5MD5(hostname + userID), + }, + subConverterConfig: { + SUBAPI: "https://SUBAPI.cmliussss.net", + SUBCONFIG: "https://raw.githubusercontent.com/cmliu/ACL4SSR/refs/heads/main/Clash/config/ACL4SSR_Online_Mini_MultiMode_CF.ini", + SUBEMOJI: false, + }, + reverseProxy: { + PROXYIP: "auto", + SOCKS5: { + enable: enableSOCKS5Proxy, + globalMode: enableGlobalSOCKS5, + account: mySOCKS5Account, + whitelist: socks5Whitelist, + }, + }, + TG: { + enable: false, + BotToken: null, + ChatID: null, + }, + CF: { + Email: null, + GlobalAPIKey: null, + AccountID: null, + APIToken: null, + UsageAPI: null, + Usage: { + success: false, + pages: 0, + workers: 0, + total: 0, + max: 100000, + }, + } + }; + + try { + let configJSON = await env.KV.get('config.json'); + if (!configJSON || resetCfg == true) { + await env.KV.put('config.json', JSON.stringify(defCfgJSON, null, 2)); + config_JSON = defCfgJSON; + } else { + config_JSON = JSON.parse(configJSON); + } + } catch (error) { + console.error(`readconfig_JSONerror: ${error.message}`); + config_JSON = defCfgJSON; + } + + config_JSON.HOST = host; + if (!config_JSON.HOSTS) config_JSON.HOSTS = [hostname]; + if (env.HOST) config_JSON.HOSTS = (await parseToArray(env.HOST)).map(h => h.toLowerCase().replace(/^https?:\/\//, '').split('/')[0].split(':')[0]); + config_JSON.UUID = userID; + config_JSON.PATH = path ? (path.startsWith('/') ? path : '/' + path) : (config_JSON.reverseProxy.SOCKS5.enable ? ('/' + config_JSON.reverseProxy.SOCKS5.enable + (config_JSON.reverseProxy.SOCKS5.globalMode ? '://' : '=') + config_JSON.reverseProxy.SOCKS5.account) : (config_JSON.reverseProxy.PROXYIP === 'auto' ? '/' : `/proxyip=${config_JSON.reverseProxy.PROXYIP}`)); + const tlsFragmentparam = config_JSON.tlsFragment == 'Shadowrocket' ? `&fragment=${encodeURIComponent('1,40-60,30-50,tlshello')}` : config_JSON.tlsFragment == 'Happ' ? `&fragment=${encodeURIComponent('3,1,tlshello')}` : ''; + if (!config_JSON.Fingerprint) config_JSON.Fingerprint = "chrome"; + if (!config_JSON.ECH) config_JSON.ECH = false; + else config_JSON.subGenerator.SUBUpdateTime = 1; // enable ECH forceChangesubupdatetimechangeTo 1 hour + const ECHLINKparam = config_JSON.ECH ? `&ech=${encodeURIComponent('cloudflare-ech.com+' + ECH_DOH)}` : ''; + config_JSON.LINK = `${config_JSON.protocolType}://${userID}@${host}:443?security=tls&type=${config_JSON.transport + ECHLINKparam}&host=${host}&fp=${config_JSON.Fingerprint}&sni=${host}&path=${encodeURIComponent(config_JSON.enable0RTT ? config_JSON.PATH + '?ed=2560' : config_JSON.PATH) + tlsFragmentparam}&encryption=none${config_JSON.skipCertVerify ? '&insecure=1&allowInsecure=1' : ''}#${encodeURIComponent(config_JSON.subGenerator.SUBNAME)}`; + config_JSON.subGenerator.TOKEN = await MD5MD5(hostname + userID); + + const initTG_JSON = { BotToken: null, ChatID: null }; + config_JSON.TG = { enable: config_JSON.TG.enable ? config_JSON.TG.enable : false, ...initTG_JSON }; + try { + const TG_TXT = await env.KV.get('tg.json'); + if (!TG_TXT) { + await env.KV.put('tg.json', JSON.stringify(initTG_JSON, null, 2)); + } else { + const TG_JSON = JSON.parse(TG_TXT); + config_JSON.TG.ChatID = TG_JSON.ChatID ? TG_JSON.ChatID : null; + config_JSON.TG.BotToken = TG_JSON.BotToken ? maskSensitive(TG_JSON.BotToken) : null; + } + } catch (error) { + console.error(`readtg.jsonerror: ${error.message}`); + } + + const initCF_JSON = { Email: null, GlobalAPIKey: null, AccountID: null, APIToken: null, UsageAPI: null }; + config_JSON.CF = { ...initCF_JSON, Usage: { success: false, pages: 0, workers: 0, total: 0, max: 100000 } }; + try { + const CF_TXT = await env.KV.get('cf.json'); + if (!CF_TXT) { + await env.KV.put('cf.json', JSON.stringify(initCF_JSON, null, 2)); + } else { + const CF_JSON = JSON.parse(CF_TXT); + if (CF_JSON.UsageAPI) { + try { + const response = await fetch(CF_JSON.UsageAPI); + const Usage = await response.json(); + config_JSON.CF.Usage = Usage; + } catch (err) { + console.error(`request CF_JSON.UsageAPI failed: ${err.message}`); + } + } else { + config_JSON.CF.Email = CF_JSON.Email ? CF_JSON.Email : null; + config_JSON.CF.GlobalAPIKey = CF_JSON.GlobalAPIKey ? maskSensitive(CF_JSON.GlobalAPIKey) : null; + config_JSON.CF.AccountID = CF_JSON.AccountID ? maskSensitive(CF_JSON.AccountID) : null; + config_JSON.CF.APIToken = CF_JSON.APIToken ? maskSensitive(CF_JSON.APIToken) : null; + config_JSON.CF.UsageAPI = null; + const Usage = await getCloudflareUsage(CF_JSON.Email, CF_JSON.GlobalAPIKey, CF_JSON.AccountID, CF_JSON.APIToken); + config_JSON.CF.Usage = Usage; + } + } + } catch (error) { + console.error(`readcf.jsonerror: ${error.message}`); + } + + config_JSON.loadtime = (performance.now() - initStarttime).toFixed(2) + 'ms'; + return config_JSON; +} + +async function genrandomIP(request, count = 16, specifiedPort = -1) { + const asnMap = { '9808': 'cmcc', '4837': 'cu', '4134': 'ct' }, asn = request.cf.asn; + const cidr_url = asnMap[asn] ? `https://raw.githubusercontent.com/cmliu/cmliu/main/CF-CIDR/${asnMap[asn]}.txt` : 'https://raw.githubusercontent.com/cmliu/cmliu/main/CF-CIDR.txt'; + const cfname = { '9808': 'CFCMCCOptimal', '4837': 'CFCUOptimal', '4134': 'CFCTOptimal' }[asn] || 'CFofficialOptimal'; + const cfport = [443, 2053, 2083, 2087, 2096, 8443]; + let cidrList = []; + try { const res = await fetch(cidr_url); cidrList = res.ok ? await parseToArray(await res.text()) : ['104.16.0.0/13']; } catch { cidrList = ['104.16.0.0/13']; } + + const generateRandomIPFromCIDR = (cidr) => { + const [baseIP, prefixLength] = cidr.split('/'), prefix = parseInt(prefixLength), hostBits = 32 - prefix; + const ipInt = baseIP.split('.').reduce((a, p, i) => a | (parseInt(p) << (24 - i * 8)), 0); + const randomOffset = Math.floor(Math.random() * Math.pow(2, hostBits)); + const mask = (0xFFFFFFFF << hostBits) >>> 0, randomIP = (((ipInt & mask) >>> 0) + randomOffset) >>> 0; + return [(randomIP >>> 24) & 0xFF, (randomIP >>> 16) & 0xFF, (randomIP >>> 8) & 0xFF, randomIP & 0xFF].join('.'); + }; + + const randomIPs = Array.from({ length: count }, () => { + const ip = generateRandomIPFromCIDR(cidrList[Math.floor(Math.random() * cidrList.length)]); + return `${ip}:${specifiedPort === -1 ? cfport[Math.floor(Math.random() * cfport.length)] : specifiedPort}#${cfname}`; + }); + return [randomIPs, randomIPs.join('\n')]; +} + +async function parseToArray(content) { + var replaced = content.replace(/[ "'\r\n]+/g, ',').replace(/,+/g, ','); + if (replaced.charAt(0) == ',') replaced = replaced.slice(1); + if (replaced.charAt(replaced.length - 1) == ',') replaced = replaced.slice(0, replaced.length - 1); + const addrArr = replaced.split(','); + return addrArr; +} + +function isValidBase64(str) { + if (typeof str !== 'string') return false; + const cleanStr = str.replace(/\s/g, ''); + if (cleanStr.length === 0 || cleanStr.length % 4 !== 0) return false; + const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/; + if (!base64Regex.test(cleanStr)) return false; + try { + atob(cleanStr); + return true; + } catch { + return false; + } +} + +function base64Decode(str) { + const bytes = new Uint8Array(atob(str).split('').map(c => c.charCodeAt(0))); + const decoder = new TextDecoder('utf-8'); + return decoder.decode(bytes); +} + +async function requestOptimalAPI(urls, defPort = '443', timeouttime = 3000) { + if (!urls?.length) return [[], [], []]; + const results = new Set(); + let subLinkPlainLINKcontent = '', needSubConvertURLs = []; + await Promise.allSettled(urls.map(async (url) => { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeouttime); + const response = await fetch(url, { signal: controller.signal }); + clearTimeout(timeoutId); + let text = ''; + try { + const buffer = await response.arrayBuffer(); + const contentType = (response.headers.get('content-type') || '').toLowerCase(); + const charset = contentType.match(/charset=([^\s;]+)/i)?.[1]?.toLowerCase() || ''; + + + let decoders = ['utf-8', 'gb2312']; // defFirst UTF-8 + if (charset.includes('gb') || charset.includes('gbk') || charset.includes('gb2312')) { + decoders = ['gb2312', 'utf-8']; // likeifSpecified GB encoding,tryFirst GB2312 + } + + + let decodeSuccess = false; + for (const decoder of decoders) { + try { + const decoded = new TextDecoder(decoder).decode(buffer); + + if (decoded && decoded.length > 0 && !decoded.includes('\ufffd')) { + text = decoded; + decodeSuccess = true; + break; + } else if (decoded && decoded.length > 0) { + + continue; + } + } catch (e) { + + continue; + } + } + + + if (!decodeSuccess) { + text = await response.text(); + } + + + if (!text || text.trim().length === 0) { + return; + } + } catch (e) { + console.error('Failed to decode response:', e); + return; + } + + + + + const preSubPlain = isValidBase64(text) ? base64Decode(text) : text; + if (preSubPlain.split('#')[0].includes('://')) { + subLinkPlainLINKcontent += preSubPlain + '\n'; // appendLINKplaincontent + return; + } + + const lines = text.trim().split('\n').map(l => l.trim()).filter(l => l); + const isCSV = lines.length > 1 && lines[0].includes(','); + const IPV6_PATTERN = /^[^\[\]]*:[^\[\]]*:[^\[\]]/; + if (!isCSV) { + lines.forEach(line => { + const hashIndex = line.indexOf('#'); + const [hostPart, remark] = hashIndex > -1 ? [line.substring(0, hashIndex), line.substring(hashIndex)] : [line, '']; + let hasPort = false; + if (hostPart.startsWith('[')) { + hasPort = /\]:(\d+)$/.test(hostPart); + } else { + const colonIndex = hostPart.lastIndexOf(':'); + hasPort = colonIndex > -1 && /^\d+$/.test(hostPart.substring(colonIndex + 1)); + } + const port = new URL(url).searchParams.get('port') || defPort; + results.add(hasPort ? line : `${hostPart}:${port}${remark}`); + }); + } else { + const headers = lines[0].split(',').map(h => h.trim()); + const dataLines = lines.slice(1); + if (headers.includes('IPaddr') && headers.includes('port') && headers.includes('dataCenter')) { + const ipIdx = headers.indexOf('IPaddr'), portIdx = headers.indexOf('port'); + const remarkIdx = headers.indexOf('country') > -1 ? headers.indexOf('country') : + headers.indexOf('city') > -1 ? headers.indexOf('city') : headers.indexOf('dataCenter'); + const tlsIdx = headers.indexOf('TLS'); + dataLines.forEach(line => { + const cols = line.split(',').map(c => c.trim()); + if (tlsIdx !== -1 && cols[tlsIdx]?.toLowerCase() !== 'true') return; + const wrappedIP = IPV6_PATTERN.test(cols[ipIdx]) ? `[${cols[ipIdx]}]` : cols[ipIdx]; + results.add(`${wrappedIP}:${cols[portIdx]}#${cols[remarkIdx]}`); + }); + } else if (headers.some(h => h.includes('IP')) && headers.some(h => h.includes('latency')) && headers.some(h => h.includes('dlSpeed'))) { + const ipIdx = headers.findIndex(h => h.includes('IP')); + const delayIdx = headers.findIndex(h => h.includes('latency')); + const speedIdx = headers.findIndex(h => h.includes('dlSpeed')); + const port = new URL(url).searchParams.get('port') || defPort; + dataLines.forEach(line => { + const cols = line.split(',').map(c => c.trim()); + const wrappedIP = IPV6_PATTERN.test(cols[ipIdx]) ? `[${cols[ipIdx]}]` : cols[ipIdx]; + results.add(`${wrappedIP}:${port}#CFOptimal ${cols[delayIdx]}ms ${cols[speedIdx]}MB/s`); + }); + } + } + } catch (e) { } + })); + + const LINKarr = subLinkPlainLINKcontent.trim() ? [...new Set(subLinkPlainLINKcontent.split(/\r?\n/).filter(line => line.trim() !== ''))] : []; + return [Array.from(results), LINKarr, needSubConvertURLs]; +} + +async function reverseProxyparamget(request) { + const url = new URL(request.url); + const { pathname, searchParams } = url; + const pathLower = pathname.toLowerCase(); + + + mySOCKS5Account = searchParams.get('socks5') || searchParams.get('http') || null; + enableGlobalSOCKS5 = searchParams.has('globalproxy') || false; + + + const proxyMatch = pathLower.match(/\/(proxyip[.=]|pyip=|ip=)(.+)/); + if (searchParams.has('proxyip')) { + const pathParamIP = searchParams.get('proxyip'); + proxyIP = pathParamIP.includes(',') ? pathParamIP.split(',')[Math.floor(Math.random() * pathParamIP.split(',').length)] : pathParamIP; + enableProxyFallback = false; + return; + } else if (proxyMatch) { + const pathParamIP = proxyMatch[1] === 'proxyip.' ? `proxyip.${proxyMatch[2]}` : proxyMatch[2]; + proxyIP = pathParamIP.includes(',') ? pathParamIP.split(',')[Math.floor(Math.random() * pathParamIP.split(',').length)] : pathParamIP; + enableProxyFallback = false; + return; + } + + + let socksMatch; + if ((socksMatch = pathname.match(/\/(socks5?|http):\/?\/?(.+)/i))) { + + enableSOCKS5Proxy = socksMatch[1].toLowerCase() === 'http' ? 'http' : 'socks5'; + mySOCKS5Account = socksMatch[2].split('#')[0]; + enableGlobalSOCKS5 = true; + + + if (mySOCKS5Account.includes('@')) { + const atIndex = mySOCKS5Account.lastIndexOf('@'); + let userPassword = mySOCKS5Account.substring(0, atIndex).replaceAll('%3D', '='); + if (/^(?:[A-Z0-9+/]{4})*(?:[A-Z0-9+/]{2}==|[A-Z0-9+/]{3}=)?$/i.test(userPassword) && !userPassword.includes(':')) { + userPassword = atob(userPassword); + } + mySOCKS5Account = `${userPassword}@${mySOCKS5Account.substring(atIndex + 1)}`; + } + } else if ((socksMatch = pathname.match(/\/(g?s5|socks5|g?http)=(.+)/i))) { + + const type = socksMatch[1].toLowerCase(); + mySOCKS5Account = socksMatch[2]; + enableSOCKS5Proxy = type.includes('http') ? 'http' : 'socks5'; + enableGlobalSOCKS5 = type.startsWith('g') || enableGlobalSOCKS5; // gs5 or ghttp startenableglobalMode + } + + + if (mySOCKS5Account) { + try { + parsedSocks5Address = await getSOCKS5account(mySOCKS5Account); + enableSOCKS5Proxy = searchParams.get('http') ? 'http' : enableSOCKS5Proxy; + } catch (err) { + console.error('parseSOCKS5addrFailed:', err.message); + enableSOCKS5Proxy = null; + } + } else enableSOCKS5Proxy = null; +} + +async function getSOCKS5account(address) { + if (address.includes('@')) { + const lastAtIndex = address.lastIndexOf('@'); + let userPassword = address.substring(0, lastAtIndex).replaceAll('%3D', '='); + const base64Regex = /^(?:[A-Z0-9+/]{4})*(?:[A-Z0-9+/]{2}==|[A-Z0-9+/]{3}=)?$/i; + if (base64Regex.test(userPassword) && !userPassword.includes(':')) userPassword = atob(userPassword); + address = `${userPassword}@${address.substring(lastAtIndex + 1)}`; + } + const atIndex = address.lastIndexOf("@"); + const [hostPart, authPart] = atIndex === -1 ? [address, undefined] : [address.substring(atIndex + 1), address.substring(0, atIndex)]; + + + let username, password; + if (authPart) { + [username, password] = authPart.split(":"); + if (!password) throw new Error('invalid SOCKS addrFormat:authMustBe "username:password" format'); + } + + + let hostname, port; + if (hostPart.includes("]:")) { // IPv6withport + [hostname, port] = [hostPart.split("]:")[0] + "]", Number(hostPart.split("]:")[1].replace(/[^\d]/g, ''))]; + } else if (hostPart.startsWith("[")) { // IPv6noneport + [hostname, port] = [hostPart, 80]; + } else { // IPv4/domain + const parts = hostPart.split(":"); + [hostname, port] = parts.length === 2 ? [parts[0], Number(parts[1].replace(/[^\d]/g, ''))] : [hostPart, 80]; + } + + if (isNaN(port)) throw new Error('invalid SOCKS addrFormat:portMustBeNumber'); + if (hostname.includes(":") && !/^\[.*\]$/.test(hostname)) throw new Error('invalid SOCKS addrFormat:IPv6 addrMustBeBracketed,like [2001:db8::1]'); + + return { username, password, hostname, port }; +} + +async function getCloudflareUsage(Email, GlobalAPIKey, AccountID, APIToken) { + const API = "https://api.cloudflare.com/client/v4"; + const sum = (a) => a?.reduce((t, i) => t + (i?.sum?.requests || 0), 0) || 0; + const cfg = { "Content-Type": "application/json" }; + + try { + if (!AccountID && (!Email || !GlobalAPIKey)) return { success: false, pages: 0, workers: 0, total: 0, max: 100000 }; + + if (!AccountID) { + const r = await fetch(`${API}/accounts`, { + method: "GET", + headers: { ...cfg, "X-AUTH-EMAIL": Email, "X-AUTH-KEY": GlobalAPIKey } + }); + if (!r.ok) throw new Error(`accountGetFailed: ${r.status}`); + const d = await r.json(); + if (!d?.result?.length) throw new Error("accountNotFound"); + const idx = d.result.findIndex(a => a.name?.toLowerCase().startsWith(Email.toLowerCase())); + AccountID = d.result[idx >= 0 ? idx : 0]?.id; + } + + const now = new Date(); + now.setUTCHours(0, 0, 0, 0); + const hdr = APIToken ? { ...cfg, "Authorization": `Bearer ${APIToken}` } : { ...cfg, "X-AUTH-EMAIL": Email, "X-AUTH-KEY": GlobalAPIKey }; + + const res = await fetch(`${API}/graphql`, { + method: "POST", + headers: hdr, + body: JSON.stringify({ + query: `query getBillingMetrics($AccountID: String!, $filter: AccountWorkersInvocationsAdaptiveFilter_InputObject) { + viewer { accounts(filter: {accountTag: $AccountID}) { + pagesFunctionsInvocationsAdaptiveGroups(limit: 1000, filter: $filter) { sum { requests } } + workersInvocationsAdaptive(limit: 10000, filter: $filter) { sum { requests } } + } } + }`, + variables: { AccountID, filter: { datetime_geq: now.toISOString(), datetime_leq: new Date().toISOString() } } + }) + }); + + if (!res.ok) throw new Error(`queryFailed: ${res.status}`); + const result = await res.json(); + if (result.errors?.length) throw new Error(result.errors[0].message); + + const acc = result?.data?.viewer?.accounts?.[0]; + if (!acc) throw new Error("accountDataNotFound"); + + const pages = sum(acc.pagesFunctionsInvocationsAdaptiveGroups); + const workers = sum(acc.workersInvocationsAdaptive); + const total = pages + workers; + const max = 100000; + console.log(`statResult - Pages: ${pages}, Workers: ${workers}, totalCnt: ${total}, limit: 100000`); + return { success: true, pages, workers, total, max }; + + } catch (error) { + console.error('getUsageError:', error.message); + return { success: false, pages: 0, workers: 0, total: 0, max: 100000 }; + } +} + +function sha224(s) { + const K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]; + const r = (n, b) => ((n >>> b) | (n << (32 - b))) >>> 0; + s = unescape(encodeURIComponent(s)); + const l = s.length * 8; s += String.fromCharCode(0x80); + while ((s.length * 8) % 512 !== 448) s += String.fromCharCode(0); + const h = [0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4]; + const hi = Math.floor(l / 0x100000000), lo = l & 0xFFFFFFFF; + s += String.fromCharCode((hi >>> 24) & 0xFF, (hi >>> 16) & 0xFF, (hi >>> 8) & 0xFF, hi & 0xFF, (lo >>> 24) & 0xFF, (lo >>> 16) & 0xFF, (lo >>> 8) & 0xFF, lo & 0xFF); + const w = []; for (let i = 0; i < s.length; i += 4)w.push((s.charCodeAt(i) << 24) | (s.charCodeAt(i + 1) << 16) | (s.charCodeAt(i + 2) << 8) | s.charCodeAt(i + 3)); + for (let i = 0; i < w.length; i += 16) { + const x = new Array(64).fill(0); + for (let j = 0; j < 16; j++)x[j] = w[i + j]; + for (let j = 16; j < 64; j++) { + const s0 = r(x[j - 15], 7) ^ r(x[j - 15], 18) ^ (x[j - 15] >>> 3); + const s1 = r(x[j - 2], 17) ^ r(x[j - 2], 19) ^ (x[j - 2] >>> 10); + x[j] = (x[j - 16] + s0 + x[j - 7] + s1) >>> 0; + } + let [a, b, c, d, e, f, g, h0] = h; + for (let j = 0; j < 64; j++) { + const S1 = r(e, 6) ^ r(e, 11) ^ r(e, 25), ch = (e & f) ^ (~e & g), t1 = (h0 + S1 + ch + K[j] + x[j]) >>> 0; + const S0 = r(a, 2) ^ r(a, 13) ^ r(a, 22), maj = (a & b) ^ (a & c) ^ (b & c), t2 = (S0 + maj) >>> 0; + h0 = g; g = f; f = e; e = (d + t1) >>> 0; d = c; c = b; b = a; a = (t1 + t2) >>> 0; + } + for (let j = 0; j < 8; j++)h[j] = (h[j] + (j === 0 ? a : j === 1 ? b : j === 2 ? c : j === 3 ? d : j === 4 ? e : j === 5 ? f : j === 6 ? g : h0)) >>> 0; + } + let hex = ''; + for (let i = 0; i < 7; i++) { + for (let j = 24; j >= 0; j -= 8)hex += ((h[i] >>> j) & 0xFF).toString(16).padStart(2, '0'); + } + return hex; +} + +async function parseAddrPort(proxyIP, targetdomain = 'dash.cloudflare.com', UUID = '00000000-0000-4000-8000-000000000000') { + if (!cachedProxyIP || !cachedProxyArray || cachedProxyIP !== proxyIP) { + proxyIP = proxyIP.toLowerCase(); + async function DoHquery(domain, recordtype) { + try { + const response = await fetch(`https://1.1.1.1/dns-query?name=${domain}&type=${recordtype}`, { + headers: { 'Accept': 'application/dns-json' } + }); + if (!response.ok) return []; + const data = await response.json(); + return data.Answer || []; + } catch (error) { + console.error(`dohQueryFailed (${recordtype}):`, error); + return []; + } + } + + function parseAddrPortStr(str) { + let addr = str, port = 443; + if (str.includes(']:')) { + const parts = str.split(']:'); + addr = parts[0] + ']'; + port = parseInt(parts[1], 10) || port; + } else if (str.includes(':') && !str.startsWith('[')) { + const colonIndex = str.lastIndexOf(':'); + addr = str.slice(0, colonIndex); + port = parseInt(str.slice(colonIndex + 1), 10) || port; + } + return [addr, port]; + } + + let allreverseProxyarr = []; + + if (proxyIP.includes('.william')) { + try { + const txtRecords = await DoHquery(proxyIP, 'TXT'); + const txtData = txtRecords.filter(r => r.type === 16).map(r => r.data); + if (txtData.length > 0) { + let data = txtData[0]; + if (data.startsWith('"') && data.endsWith('"')) data = data.slice(1, -1); + const prefixes = data.replace(/\\010/g, ',').replace(/\n/g, ',').split(',').map(s => s.trim()).filter(Boolean); + allreverseProxyarr = prefixes.map(prefix => parseAddrPortStr(prefix)); + } + } catch (error) { + console.error('parseWilliamFailed:', error); + } + } else { + let [addr, port] = parseAddrPortStr(proxyIP); + + if (proxyIP.includes('.tp')) { + const tpMatch = proxyIP.match(/\.tp(\d+)/); + if (tpMatch) port = parseInt(tpMatch[1], 10); + } + + + const ipv4Regex = /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/; + const ipv6Regex = /^\[?([a-fA-F0-9:]+)\]?$/; + + if (!ipv4Regex.test(addr) && !ipv6Regex.test(addr)) { + + const [aRecords, aaaaRecords] = await Promise.all([ + DoHquery(addr, 'A'), + DoHquery(addr, 'AAAA') + ]); + + const ipv4List = aRecords.filter(r => r.type === 1).map(r => r.data); + const ipv6List = aaaaRecords.filter(r => r.type === 28).map(r => `[${r.data}]`); + const ipAddresses = [...ipv4List, ...ipv6List]; + + allreverseProxyarr = ipAddresses.length > 0 + ? ipAddresses.map(ip => [ip, port]) + : [[addr, port]]; + } else { + allreverseProxyarr = [[addr, port]]; + } + } + const sortedArr = allreverseProxyarr.sort((a, b) => a[0].localeCompare(b[0])); + const targetRootdomain = targetdomain.includes('.') ? targetdomain.split('.').slice(-2).join('.') : targetdomain; + let randSeed = [...(targetRootdomain + UUID)].reduce((a, c) => a + c.charCodeAt(0), 0); + console.log(`[proxyParse] randSeed: ${randSeed}\ntargetSite: ${targetRootdomain}`) + const shuffled = [...sortedArr].sort(() => (randSeed = (randSeed * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff - 0.5); + cachedProxyArray = shuffled.slice(0, 8); + console.log(`[proxyParse] parseComplete total: ${cachedProxyArray.length}\n${cachedProxyArray.map(([ip, port], index) => `${index + 1}. ${ip}:${port}`).join('\n')}`); + cachedProxyIP = proxyIP; + } else console.log(`[proxyParse] readCache total: ${cachedProxyArray.length}\n${cachedProxyArray.map(([ip, port], index) => `${index + 1}. ${ip}:${port}`).join('\n')}`); + return cachedProxyArray; +} + +async function SOCKS5availabilityCheck(proxyProto = 'socks5', proxyparam) { + const startTime = Date.now(); + try { parsedSocks5Address = await getSOCKS5account(proxyparam); } catch (err) { return { success: false, error: err.message, proxy: proxyProto + "://" + proxyparam, responseTime: Date.now() - startTime }; } + const { username, password, hostname, port } = parsedSocks5Address; + const fullProxyparam = username && password ? `${username}:${password}@${hostname}:${port}` : `${hostname}:${port}`; + try { + const initialData = new Uint8Array(0); + const tcpSocket = proxyProto == 'socks5' ? await socks5Connect('check.socks5.090227.xyz', 80, initialData) : await httpConnect('check.socks5.090227.xyz', 80, initialData); + if (!tcpSocket) return { success: false, error: 'cannotConnectToProxy', proxy: proxyProto + "://" + fullProxyparam, responseTime: Date.now() - startTime }; + try { + const writer = tcpSocket.writable.getWriter(), encoder = new TextEncoder(); + await writer.write(encoder.encode(`GET /cdn-cgi/trace HTTP/1.1\r\nHost: check.socks5.090227.xyz\r\nConnection: close\r\n\r\n`)); + writer.releaseLock(); + const reader = tcpSocket.readable.getReader(), decoder = new TextDecoder(); + let response = ''; + try { while (true) { const { done, value } = await reader.read(); if (done) break; response += decoder.decode(value, { stream: true }); } } finally { reader.releaseLock(); } + await tcpSocket.close(); + return { success: true, proxy: proxyProto + "://" + fullProxyparam, ip: response.match(/ip=(.*)/)[1], loc: response.match(/loc=(.*)/)[1], responseTime: Date.now() - startTime }; + } catch (error) { + try { await tcpSocket.close(); } catch (e) { console.log('errorClosingConn:', e); } + return { success: false, error: error.message, proxy: proxyProto + "://" + fullProxyparam, responseTime: Date.now() - startTime }; + } + } catch (error) { return { success: false, error: error.message, proxy: proxyProto + "://" + fullProxyparam, responseTime: Date.now() - startTime }; } +} + +async function nginx() { + return ` + + + + Welcome to nginx! + + + +

Welcome to nginx!

+

If you see this page, the nginx web server is successfully installed and + working. Further configuration is required.

+ +

For online documentation and support please refer to + nginx.org.
+ Commercial support is available at + nginx.com.

+ +

Thank you for using nginx.

+ + + ` +} + +async function html1101(host, clientIP) { + const now = new Date(); + const formattimestamp = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') + ':' + String(now.getSeconds()).padStart(2, '0'); + const randStr = Array.from(crypto.getRandomValues(new Uint8Array(8))).map(b => b.toString(16).padStart(2, '0')).join(''); + + return ` + + + + + +Worker threw exception | ${host} | Cloudflare + + + + + + + + + + + + + + + + +
+ +
+
+

+ Error + 1101 + Ray ID: ${randStr} • ${formattimestamp} UTC +

+

Worker threw exception

+
+ +
+ +
+
+
+

What happened?

+

You've requested a page on a website (${host}) that is on the Cloudflare network. An unknown error occurred while rendering the page.

+
+ +
+

What can I do?

+

If you are the owner of this website:
refer to Workers - Errors and Exceptions and check Workers Logs for ${host}.

+
+ +
+
+ + + +
+
+ + + +`; +} + diff --git a/x27cn-github/worker.js b/x27cn-github/worker.js new file mode 100644 index 0000000..21155c2 --- /dev/null +++ b/x27cn-github/worker.js @@ -0,0 +1 @@ +import{connect as e}from"cloudflare:sockets";const t=["(◕‿◕)","(。◕‿◕。)","(◠‿◠)","(✿◠‿◠)","(◡‿◡)","(◕ᴗ◕)","(◔‿◔)","(✧ω✧)","(◕◡◕)","(◠ᴗ◠)","♪(´▽`)","(◕‿◕✿)","(✿╹◡╹)","(◕ˇ∀ˇ◕)","(◔◡◔)"],n=["龖","龘","靐","齉","齾","爨","灪","麤","鱻","驫","骉","羴","猋","蟲","贔","矗","飝","厵","靇","雥"];function s(e,s="x27cn2026"){if(!e)return"";const r=(new TextEncoder).encode(s),o=(new TextEncoder).encode(e),a=new Uint8Array(o.length);for(let e=0;e>5),t=t+e&255,a[e]=t}const i=Array.from(a).map(e=>e.toString(16).padStart(2,"0")).join("");let c="";const l=o.length;for(let e=0;e0){c+=t[(l+e)%t.length]}if(e%16==0){c+=n[(2*l+e)%n.length]}}return t[l%t.length]+n[l%n.length]+c+(n[3*l%n.length]+t[2*l%t.length])}let r,o,a,i="",c=null,l=!1,d="",u={},p=0,h=!0,f="https://doh.cmliussss.net/CMLiussss",g=["*tapecontent.net","*cloudatacdn.com","*loadshare.org","*cdn-centaurus.com","scholar.google.com"];const m="https://edt-pages.github.io";export default{async fetch(e,t,n){const o=new URL(e.url),a=e.headers.get("User-Agent")||"null",p=e.headers.get("Upgrade"),C=t.ADMIN||t.admin||t.PASSWORD||t.password||t.pswd||t.TOKEN||t.KEY||t.UUID||t.uuid||"cfspider-public",A=t.KEY||"cfspider-default-key",T=await S(C+A),P=/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/,_=t.UUID||t.uuid,N=_&&P.test(_)?_.toLowerCase():[T.slice(0,8),T.slice(8,12),"4"+T.slice(13,16),"8"+T.slice(17,20),T.slice(20)].join("-"),j=(t.HOST?(await v(t.HOST)).map(e=>e.toLowerCase().replace(/^https?:\/\//,"").split("/")[0].split(":")[0]):[o.hostname])[0];if(t.PROXYIP){const e=await v(t.PROXYIP);i=e[Math.floor(Math.random()*e.length)],h=!1}else i=(e.cf.colo+".PrOxYIp.CmLiUsSsS.nEt").toLowerCase();const D=e.headers.get("X-Real-IP")||e.headers.get("CF-Connecting-IP")||e.headers.get("X-Forwarded-For")||e.headers.get("True-Client-IP")||e.headers.get("Fly-Client-IP")||e.headers.get("X-Appengine-Remote-Addr")||e.headers.get("X-Forwarded-For")||e.headers.get("X-Real-IP")||e.headers.get("X-Cluster-Client-IP")||e.cf?.clientTcpRtt||"unknownIP";if(t.GO2SOCKS5&&(g=await v(t.GO2SOCKS5)),f=t.ECH_DOH||t.DOH||f,p&&"websocket"===p){if(C){await async function(e){const t=new URL(e.url),{pathname:n,searchParams:s}=t,r=n.toLowerCase();d=s.get("socks5")||s.get("http")||null,l=s.has("globalproxy")||!1;const o=r.match(/\/(proxyip[.=]|pyip=|ip=)(.+)/);if(s.has("proxyip")){const e=s.get("proxyip");return i=e.includes(",")?e.split(",")[Math.floor(Math.random()*e.split(",").length)]:e,void(h=!1)}if(o){const e="proxyip."===o[1]?`proxyip.${o[2]}`:o[2];return i=e.includes(",")?e.split(",")[Math.floor(Math.random()*e.split(",").length)]:e,void(h=!1)}let a;if(a=n.match(/\/(socks5?|http):\/?\/?(.+)/i)){if(c="http"===a[1].toLowerCase()?"http":"socks5",d=a[2].split("#")[0],l=!0,d.includes("@")){const e=d.lastIndexOf("@");let t=d.substring(0,e).replaceAll("%3D","=");/^(?:[A-Z0-9+/]{4})*(?:[A-Z0-9+/]{2}==|[A-Z0-9+/]{3}=)?$/i.test(t)&&!t.includes(":")&&(t=atob(t)),d=`${t}@${d.substring(e+1)}`}}else if(a=n.match(/\/(g?s5|socks5|g?http)=(.+)/i)){const e=a[1].toLowerCase();d=a[2],c=e.includes("http")?"http":"socks5",l=e.startsWith("g")||l}if(d)try{u=await E(d),c=s.get("http")?"http":c}catch(e){console.error("parseSOCKS5addrFailed:",e.message),c=null}else c=null}(e);const n=new URL(e.url).pathname;let s="";if(n.includes("two_proxy=")){const e=n.match(/two_proxy=([^&]+)/);e&&(s=decodeURIComponent(decodeURIComponent(e[1])))}const r=s||t.TWO_PROXY||t.two_proxy||"";return await async function(e,t,n=""){const s=new WebSocketPair,[r,o]=Object.values(s);o.accept();let a={socket:null},i=!1;const c=e.headers.get("sec-websocket-protocol")||"",l=function(e,t){let n=!1;return new ReadableStream({start(s){e.addEventListener("message",e=>{n||s.enqueue(e.data)}),e.addEventListener("close",()=>{n||(b(e),s.close())}),e.addEventListener("error",e=>s.error(e));const{earlyData:r,error:o}=function(e){if(!e)return{error:null};try{const t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),n=new Uint8Array(t.length);for(let e=0;e=2&&(u={hostname:e[0],port:parseInt(e[1],10),username:e[2]||"",password:e[3]||""},console.log(`[twoProxy] enabled: ${u.hostname}:${u.port}`))}return l.pipeTo(new WritableStream({async write(e){if(i)return await y(e,o,null);if(a.socket){const t=a.socket.writable.getWriter();return await t.write(e),void t.releaseLock()}if(null===d){const t=new Uint8Array(e);d=t.byteLength>=58&&13===t[56]&&10===t[57]}if(a.socket){const t=a.socket.writable.getWriter();return await t.write(e),void t.releaseLock()}if(d){const{port:n,hostname:s,rawClientData:r}=function(e,t){const n=function(e){const t=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],n=(e,t)=>(e>>>t|e<<32-t)>>>0,s=8*(e=unescape(encodeURIComponent(e))).length;e+=String.fromCharCode(128);for(;8*e.length%512!=448;)e+=String.fromCharCode(0);const r=[3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428],o=Math.floor(s/4294967296),a=4294967295&s;e+=String.fromCharCode(o>>>24&255,o>>>16&255,o>>>8&255,255&o,a>>>24&255,a>>>16&255,a>>>8&255,255&a);const i=[];for(let t=0;t>>3,r=n(s[e-2],17)^n(s[e-2],19)^s[e-2]>>>10;s[e]=s[e-16]+t+s[e-7]+r>>>0}let[o,a,c,l,d,u,p,h]=r;for(let e=0;e<64;e++){const r=h+(n(d,6)^n(d,11)^n(d,25))+(d&u^~d&p)+t[e]+s[e]>>>0,i=o&a^o&c^a&c;h=p,p=u,u=d,d=l+r>>>0,l=c,c=a,a=o,o=r+((n(o,2)^n(o,13)^n(o,22))+i>>>0)>>>0}for(let e=0;e<8;e++)r[e]=r[e]+(0===e?o:1===e?a:2===e?c:3===e?l:4===e?d:5===e?u:6===e?p:h)>>>0}let c="";for(let e=0;e<7;e++)for(let t=24;t>=0;t-=8)c+=(r[e]>>>t&255).toString(16).padStart(2,"0");return c}(t);if(e.byteLength<56)return{hasError:!0,message:"invalid data"};let s=56;if(13!==new Uint8Array(e.slice(56,57))[0]||10!==new Uint8Array(e.slice(57,58))[0])return{hasError:!0,message:"invalid header format"};if((new TextDecoder).decode(e.slice(0,s))!==n)return{hasError:!0,message:"invalid password"};const r=e.slice(s+2);if(r.byteLength<6)return{hasError:!0,message:"invalid S5 request data"};const o=new DataView(r);if(1!==o.getUint8(0))return{hasError:!0,message:"unsupported command, only TCP is allowed"};const a=o.getUint8(1);let i=0,c=2,l="";switch(a){case 1:i=4,l=new Uint8Array(r.slice(c,c+i)).join(".");break;case 3:i=new Uint8Array(r.slice(c,c+1))[0],c+=1,l=(new TextDecoder).decode(r.slice(c,c+i));break;case 4:i=16;const e=new DataView(r.slice(c,c+i)),t=[];for(let n=0;n<8;n++)t.push(e.getUint16(2*n).toString(16));l=t.join(":");break;default:return{hasError:!0,message:`invalid addressType is ${a}`}}if(!l)return{hasError:!0,message:`address is empty, addressType is ${a}`};const d=c+i,u=r.slice(d,d+2),p=new DataView(u).getUint16(0);return{hasError:!1,addressType:a,port:p,hostname:l,rawClientData:r.slice(d+4)}}(e,t);if(x(s))throw new Error("Speedtest site is blocked");await w(s,n,r,o,null,a,t,u)}else{const{port:n,hostname:s,rawIndex:r,version:c,isUDP:l}=function(e,t){if(e.byteLength<24)return{hasError:!0,message:"Invalid data"};const n=new Uint8Array(e.slice(0,1));if(function(e,t=0){const n=[...e.slice(t,t+16)].map(e=>e.toString(16).padStart(2,"0")).join("");return`${n.substring(0,8)}-${n.substring(8,12)}-${n.substring(12,16)}-${n.substring(16,20)}-${n.substring(20)}`}(new Uint8Array(e.slice(1,17)))!==t)return{hasError:!0,message:"Invalid uuid"};const s=new Uint8Array(e.slice(17,18))[0],r=new Uint8Array(e.slice(18+s,19+s))[0];let o=!1;if(1===r);else{if(2!==r)return{hasError:!0,message:"Invalid command"};o=!0}const a=19+s,i=new DataView(e.slice(a,a+2)).getUint16(0);let c=a+2,l=0,d=c+1,u="";const p=new Uint8Array(e.slice(c,d))[0];switch(p){case 1:l=4,u=new Uint8Array(e.slice(d,d+l)).join(".");break;case 2:l=new Uint8Array(e.slice(d,d+1))[0],d+=1,u=(new TextDecoder).decode(e.slice(d,d+l));break;case 3:l=16;const t=[],n=new DataView(e.slice(d,d+l));for(let e=0;e<8;e++)t.push(n.getUint16(2*e).toString(16));u=t.join(":");break;default:return{hasError:!0,message:`Invalid address type: ${p}`}}return u?{hasError:!1,addressType:p,port:i,hostname:u,isUDP:o,rawIndex:d+l,version:n}:{hasError:!0,message:`Invalid address: ${p}`}}(e,t);if(x(s))throw new Error("Speedtest site is blocked");if(l){if(53!==n)throw new Error("UDP is not supported");i=!0}const d=new Uint8Array([c[0],0]),p=e.slice(r);if(i)return y(p,o,d);await w(s,n,p,o,d,a,t,u)}}})).catch(e=>{}),new Response(null,{status:101,webSocket:r})}(e,N,r)}}else{if("http:"===o.protocol)return Response.redirect(o.href.replace(`http://${o.hostname}`,`https://${o.hostname}`),301);const i=o.pathname.slice(1).toLowerCase(),c=("false"!==t.NEW_IP&&t.NEW_IP,!_||!P.test(_)),l=t.TWO_PROXY||t.two_proxy||"";if(""===i||"/"===i){const t=e.cf?.colo||"UNKNOWN",n="/"+N+(l?"?two_proxy="+encodeURIComponent(l):""),r="vless://"+N+"@"+o.hostname+":443?security=tls&type=ws&host="+o.hostname+"&sni="+o.hostname+"&path="+encodeURIComponent(n)+"&encryption=none#Node-"+t,a={status:"online",version:"1.8.7",colo:t,host:o.hostname,uuid:N,vless:r,two_proxy:l||null};return new Response(s(JSON.stringify(a)),{headers:{"Content-Type":"text/plain","Access-Control-Allow-Origin":"*","X-Enc":"x27cn"}})}if("api/proxyip"===i){const n=e.cf?.colo||"UNKNOWN",r=(n+".proxyip.cmliussss.net").toLowerCase(),o=t.PROXYIP||"",a={colo:n,default:r,hk:"proxyip.cfspider.com",env:o||null,current:o||r,options:{default:{name:"nlNode",address:r},hk:{name:"hkNode",address:"proxyip.cfspider.com"}}};return new Response(s(JSON.stringify(a)),{headers:{"Content-Type":"text/plain","Access-Control-Allow-Origin":"*","X-Enc":"x27cn"}})}if("api/uuid"===i||"api/config"===i){const e="false"!==t.NEW_IP&&"0"!==t.NEW_IP,n=t.TWO_PROXY||t.two_proxy||"",r={host:o.hostname,new_ip:e,version:"1.8.7",is_default_uuid:c,two_proxy_enabled:!!n};if(c?(r.uuid=N,r.vless_path=n?"/"+N+"?two_proxy="+encodeURIComponent(n):"/"+N):n&&(r.two_proxy=n),n){const e=n.split(":");r.two_proxy_host=e[0]||"",r.two_proxy_port=e[1]||""}return new Response(s(JSON.stringify(r)),{headers:{"Content-Type":"text/plain","Access-Control-Allow-Origin":"*","X-Enc":"x27cn"}})}if("proxy"===i||i.startsWith("proxy?")){const n=o.searchParams.get("url"),s=o.searchParams.get("method")||"GET",r=o.searchParams.get("two_proxy");if(!n)return new Response(JSON.stringify({error:"Missing url parameter"}),{status:400,headers:{"Content-Type":"application/json"}});try{const o={};for(const[t,n]of e.headers)if(t.toLowerCase().startsWith("x-custom-header-")){o[t.substring(18)]=n}let a;const i=r||t.TWO_PROXY||t.two_proxy||"";if(i){const t=i.split(":"),r=t[0],a=parseInt(t[1])||3128,c=t[2]||"",l=t[3]||"",d=new URL(n),u=d.hostname,p=(d.port||d.protocol,"https:"===d.protocol),{connect:h}=await import("cloudflare:sockets");if(p)return new Response(JSON.stringify({error:"HTTPS + two_proxy notSupportedViaProxyAPI。useCfspiderGetWithTwoProxy。",hint:"client.get(url, cf_proxies=..., uuid=..., two_proxy=...)",reason:"Workers /proxy API onlyHTTPTwoProxySupported"}),{status:501,headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}});{const t=h({hostname:r,port:a}),i=t.writable.getWriter(),d=t.readable.getReader();let p=`${s} ${n} HTTP/1.1\r\nHost: ${u}\r\n`;if(c&&l){p+=`Proxy-Authorization: Basic ${btoa(`${c}:${l}`)}\r\n`}for(const[e,t]of Object.entries(o))p+=`${e}: ${t}\r\n`;p+="Connection: close\r\n\r\n",await i.write((new TextEncoder).encode(p));let f=new Uint8Array(0);for(;;){const{value:e,done:t}=await d.read();if(t)break;const n=new Uint8Array(f.length+e.length);n.set(f),n.set(e,f.length),f=n}const g=(new TextDecoder).decode(f),m=g.indexOf("\r\n\r\n"),w=g.substring(0,m),y=f.slice((new TextEncoder).encode(g.substring(0,m+4)).length),b=w.split("\r\n")[0],C=parseInt(b.split(" ")[1])||200,x=new Headers;return w.split("\r\n").slice(1).forEach(e=>{const[t,...n]=e.split(":");t&&n.length&&x.set(t.trim(),n.join(":").trim())}),x.set("Access-Control-Allow-Origin","*"),x.set("X-CF-Colo",e.cf?.colo||"unknown"),x.set("X-Worker-Version","1.8.6"),x.set("X-Two-Proxy","enabled"),new Response(y,{status:C,headers:x})}}{const t=new Request(n,{method:s,headers:o,body:"GET"!==s&&"HEAD"!==s?e.body:null});a=await fetch(t);const r=new Headers(a.headers);return r.set("Access-Control-Allow-Origin","*"),r.set("X-CF-Colo",e.cf?.colo||"unknown"),r.set("X-Worker-Version","1.8.6"),new Response(a.body,{status:a.status,headers:r})}}catch(e){return new Response(JSON.stringify({error:e.message}),{status:500,headers:{"Content-Type":"application/json"}})}}if("api/config/new_ip"===i&&"POST"===e.method){const e="false"!==t.NEW_IP&&"0"!==t.NEW_IP;return new Response(JSON.stringify({new_ip:e,message:"setNEWIPViaDashboard"}),{headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}})}if(!C)return fetch(m+"/noADMIN").then(e=>{const t=new Headers(e.headers);return t.set("Cache-Control","no-store, no-cache, must-revalidate, proxy-revalidate"),t.set("Pragma","no-cache"),t.set("Expires","0"),new Response(e.body,{status:404,statusText:e.statusText,headers:t})});if(t.KV&&"function"==typeof t.KV.get){const s=o.pathname.slice(1).toLowerCase(),i=o.pathname.slice(1);if(i===A&&"doNotModifyDefaultKey"!==A){const e=new URLSearchParams(o.search);return e.set("token",await S(j+N)),new Response("redirecting...",{status:302,headers:{Location:`/sub?${e.toString()}`}})}if("login"===s){const t=e.headers.get("Cookie")||"",n=t.split(";").find(e=>e.trim().startsWith("auth="))?.split("=")[1];if(n==await S(a+A+C))return new Response("redirecting...",{status:302,headers:{Location:"/admin"}});if("POST"===e.method){const t=await e.text();if(new URLSearchParams(t).get("password")===C){const e=new Response(JSON.stringify({success:!0}),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}});return e.headers.set("Set-Cookie",`auth=${await S(a+A+C)}; Path=/; Max-Age=86400; HttpOnly`),e}}return fetch(m+"/login")}if("admin"===s||s.startsWith("admin/")){const c=e.headers.get("Cookie")||"",l=c.split(";").find(e=>e.trim().startsWith("auth="))?.split("=")[1];if(!l||l!==await S(a+A+C))return new Response("redirecting...",{status:302,headers:{Location:"/login"}});if("admin/log.json"===s){const e=await t.KV.get("log.json")||"[]";return new Response(e,{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}if("admin/getCloudflareUsage"===i)try{const e=await O(o.searchParams.get("Email"),o.searchParams.get("GlobalAPIKey"),o.searchParams.get("AccountID"),o.searchParams.get("APIToken"));return new Response(JSON.stringify(e,null,2),{status:200,headers:{"Content-Type":"application/json"}})}catch(e){const t={msg:"queryRequestFailed,reason:"+e.message,error:e.message};return new Response(JSON.stringify(t,null,2),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}else{if("admin/getADDAPI"===i){if(o.searchParams.get("url")){const e=o.searchParams.get("url");try{new URL(e);const t=await k([e],o.searchParams.get("port")||"443"),n=t[0].length>0?t[0]:t[1];return new Response(JSON.stringify({success:!0,data:n},null,2),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){const t={msg:"verifyAPIFailed,reason:"+e.message,error:e.message};return new Response(JSON.stringify(t,null,2),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}}return new Response(JSON.stringify({success:!1,data:[]},null,2),{status:403,headers:{"Content-Type":"application/json;charset=utf-8"}})}if("admin/check"===s){let e;if(o.searchParams.has("socks5"))e=await R("socks5",o.searchParams.get("socks5"));else{if(!o.searchParams.has("http"))return new Response(JSON.stringify({error:"missingProxyParam"}),{status:400,headers:{"Content-Type":"application/json;charset=utf-8"}});e=await R("http",o.searchParams.get("http"))}return new Response(JSON.stringify(e,null,2),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}}if(r=await I(t,j,N,t.PATH),"admin/init"===s)try{return r=await I(t,j,N,t.PATH,!0),n.waitUntil($(t,e,D,"Init_Config",r)),r.init="configResetToDefault",new Response(JSON.stringify(r,null,2),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){const t={msg:"configResetFailed,reason:"+e.message,error:e.message};return new Response(JSON.stringify(t,null,2),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}else if("POST"===e.method)if("admin/config.json"===s)try{const s=await e.json();return s.UUID&&s.HOST?(await t.KV.put("config.json",JSON.stringify(s,null,2)),n.waitUntil($(t,e,D,"Save_Config",r)),new Response(JSON.stringify({success:!0,message:"configSaved"}),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})):new Response(JSON.stringify({error:"incompleteConfig"}),{status:400,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){return console.error("saveConfigFailed:",e),new Response(JSON.stringify({error:"saveConfigFailed: "+e.message}),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}else if("admin/cf.json"===s)try{const s=await e.json(),o={Email:null,GlobalAPIKey:null,AccountID:null,APIToken:null,UsageAPI:null};if(!s.init||!0!==s.init)if(s.Email&&s.GlobalAPIKey)o.Email=s.Email,o.GlobalAPIKey=s.GlobalAPIKey;else if(s.AccountID&&s.APIToken)o.AccountID=s.AccountID,o.APIToken=s.APIToken;else{if(!s.UsageAPI)return new Response(JSON.stringify({error:"incompleteConfig"}),{status:400,headers:{"Content-Type":"application/json;charset=utf-8"}});o.UsageAPI=s.UsageAPI}return await t.KV.put("cf.json",JSON.stringify(o,null,2)),n.waitUntil($(t,e,D,"Save_Config",r)),new Response(JSON.stringify({success:!0,message:"configSaved"}),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){return console.error("saveConfigFailed:",e),new Response(JSON.stringify({error:"saveConfigFailed: "+e.message}),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}else if("admin/tg.json"===s)try{const s=await e.json();if(s.init&&!0===s.init){const e={BotToken:null,ChatID:null};await t.KV.put("tg.json",JSON.stringify(e,null,2))}else{if(!s.BotToken||!s.ChatID)return new Response(JSON.stringify({error:"incompleteConfig"}),{status:400,headers:{"Content-Type":"application/json;charset=utf-8"}});await t.KV.put("tg.json",JSON.stringify(s,null,2))}return n.waitUntil($(t,e,D,"Save_Config",r)),new Response(JSON.stringify({success:!0,message:"configSaved"}),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){return console.error("saveConfigFailed:",e),new Response(JSON.stringify({error:"saveConfigFailed: "+e.message}),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}else{if("admin/ADD.txt"!==i)return new Response(JSON.stringify({error:"unsupportedPOSTPath"}),{status:404,headers:{"Content-Type":"application/json;charset=utf-8"}});try{const s=await e.text();return await t.KV.put("ADD.txt",s),n.waitUntil($(t,e,D,"Save_Custom_IPs",r)),new Response(JSON.stringify({success:!0,message:"customIPSaved"}),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){return console.error("saveCustomIPfailed:",e),new Response(JSON.stringify({error:"saveCustomIPfailed: "+e.message}),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}}else{if("admin/config.json"===s)return new Response(JSON.stringify(r,null,2),{status:200,headers:{"Content-Type":"application/json"}});if("admin/ADD.txt"===i){let n=await t.KV.get("ADD.txt")||"null";return"null"==n&&(n=(await U(e,r.subGenerator.localIPPool.randomCount,r.subGenerator.localIPPool.specifiedPort))[1]),new Response(n,{status:200,headers:{"Content-Type":"text/plain;charset=utf-8",asn:e.cf.asn}})}if("admin/cf.json"===s)return new Response(JSON.stringify(e.cf,null,2),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}return n.waitUntil($(t,e,D,"Admin_Login",r)),fetch(m+"/admin")}if("logout"===s||P.test(s)){const e=new Response("redirecting...",{status:302,headers:{Location:"/login"}});return e.headers.set("Set-Cookie","auth=; Path=/; Max-Age=0; HttpOnly"),e}if("sub"===s){const s=await S(j+N);if(o.searchParams.get("token")===s){r=await I(t,j,N,t.PATH),n.waitUntil($(t,e,D,"Get_SUB",r));const i=a.toLowerCase(),c=4102329600,l=Date.now(),d=new Date(l);d.setHours(0,0,0,0);const u=Math.floor((l-d.getTime())/864e5*24*1099511627776/2);let p=u,h=u,g=26388279066624;r.CF.Usage.success&&(p=r.CF.Usage.pages,h=r.CF.Usage.workers,g=Number.isFinite(r.CF.Usage.max)?r.CF.Usage.max/1e3*1024:102400);const m={"content-type":"text/plain; charset=utf-8","Profile-Update-Interval":r.subGenerator.SUBUpdateTime,"Profile-web-page-url":o.protocol+"//"+o.host+"/admin","Subscription-Userinfo":`upload=${p}; download=${h}; total=${g}; expire=${c}`,"Cache-Control":"no-store"},w=o.searchParams.has("b64")||o.searchParams.has("base64")||e.headers.get("subconverter-request")||e.headers.get("subconverter-version")||i.includes("subconverter")||i.includes("CF-Workers-SUB".toLowerCase())?"mixed":o.searchParams.has("target")?o.searchParams.get("target"):o.searchParams.has("clash")||i.includes("clash")||i.includes("meta")||i.includes("mihomo")?"clash":o.searchParams.has("sb")||o.searchParams.has("singbox")||i.includes("singbox")||i.includes("sing-box")?"singbox":o.searchParams.has("surge")||i.includes("surge")?"surge&ver=4":o.searchParams.has("quanx")||i.includes("quantumult")?"quanx":o.searchParams.has("loon")||i.includes("loon")?"loon":"mixed";i.includes("mozilla")||(m["Content-Disposition"]=`attachment; filename*=utf-8''${encodeURIComponent(r.subGenerator.SUBNAME)}`);const y=o.searchParams.has("surge")||i.includes("surge")?"trojan":r.protocolType;let b="";if("mixed"===w){const n=r.enable0RTT?r.PATH+"?ed=2560":r.PATH,s="Shadowrocket"==r.tlsFragment?`&fragment=${encodeURIComponent("1,40-60,30-50,tlshello")}`:"Happ"==r.tlsFragment?`&fragment=${encodeURIComponent("3,1,tlshello")}`:"";let a=[],i="";if(!o.searchParams.has("sub")&&r.subGenerator.local){const n=r.subGenerator.localIPPool.randomIP?(await U(e,r.subGenerator.localIPPool.randomCount,r.subGenerator.localIPPool.specifiedPort))[0]:await t.KV.get("ADD.txt")?await v(await t.KV.get("ADD.txt")):(await U(e,r.subGenerator.localIPPool.randomCount,r.subGenerator.localIPPool.specifiedPort))[0],s=[],o=[],c=[];for(const e of n)if(e.toLowerCase().startsWith("https://"))s.push(e);else if(e.toLowerCase().includes("://"))if(e.includes("#")){const t=e.split("#");c.push(t[0]+"#"+encodeURIComponent(decodeURIComponent(t[1])))}else c.push(e);else o.push(e);const l=await k(s),d=[...new Set(c.concat(l[1]))];i=d.length>0?d.join("\n")+"\n":"";const u=l[0];a=[...new Set(o.concat(u))]}else{let e=o.searchParams.get("sub")||r.subGenerator.SUB;e=e&&!/^https?:\/\//i.test(e)?`https://${e}`:e;const t=`${e}/sub?host=example.com&uuid=00000000-0000-4000-8000-000000000000`;try{const e=await fetch(t,{headers:{"User-Agent":"v2rayN/edgetunnel (https://github.com/cmliu/edgetunnel)"}});if(!e.ok)return new Response("subGenError:"+e.statusText,{status:e.status});const n=atob(await e.text()),s=n.includes("\r\n")?n.split("\r\n"):n.split("\n");for(const e of s)if(e.trim())if(e.includes("00000000-0000-4000-8000-000000000000")&&e.includes("example.com")){const t=e.match(/:\/\/[^@]+@([^?]+)/);if(t){let n=t[1],s="";const r=e.match(/#(.+)$/);r&&(s="#"+decodeURIComponent(r[1])),a.push(n+s)}}else i+=e+"\n"}catch(e){return new Response("subGenError:"+e.message,{status:403})}}const c=r.ECH?`&ech=${encodeURIComponent("cloudflare-ech.com+"+f)}`:"";b=i+a.map(e=>{const t=e.match(/^(\[[\da-fA-F:]+\]|[\d.]+|[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*)(?::(\d+))?(?:#(.+))?$/);let o,a,i="443";return t?(o=t[1],i=t[2]||"443",a=t[3]||o,`${y}://00000000-0000-4000-8000-000000000000@${o}:${i}?security=tls&type=${r.transport+c}&host=example.com&fp=${r.Fingerprint}&sni=example.com&path=${encodeURIComponent(r.randomPath?function(){const e=["about","account","acg","act","activity","ad","ads","ajax","album","albums","anime","api","app","apps","archive","archives","article","articles","ask","auth","avatar","bbs","bd","blog","blogs","book","books","bt","buy","cart","category","categories","cb","channel","channels","chat","china","city","class","classify","clip","clips","club","cn","code","collect","collection","comic","comics","community","company","config","contact","content","course","courses","cp","data","detail","details","dh","directory","discount","discuss","dl","dload","doc","docs","document","documents","doujin","download","downloads","drama","edu","en","ep","episode","episodes","event","events","f","faq","favorite","favourites","favs","feedback","file","files","film","films","forum","forums","friend","friends","game","games","gif","go","go.html","go.php","group","groups","help","home","hot","htm","html","image","images","img","index","info","intro","item","items","ja","jp","jump","jump.html","jump.php","jumping","knowledge","lang","lesson","lessons","lib","library","link","links","list","live","lives","m","mag","magnet","mall","manhua","map","member","members","message","messages","mobile","movie","movies","music","my","new","news","note","novel","novels","online","order","out","out.html","out.php","outbound","p","page","pages","pay","payment","pdf","photo","photos","pic","pics","picture","pictures","play","player","playlist","post","posts","product","products","program","programs","project","qa","question","rank","ranking","read","readme","redirect","redirect.html","redirect.php","reg","register","res","resource","retrieve","sale","search","season","seasons","section","seller","series","service","services","setting","settings","share","shop","show","shows","site","soft","sort","source","special","star","stars","static","stock","store","stream","streaming","streams","student","study","tag","tags","task","teacher","team","tech","temp","test","thread","tool","tools","topic","topics","torrent","trade","travel","tv","txt","type","u","upload","uploads","url","urls","user","users","v","version","video","videos","view","vip","vod","watch","web","wenku","wiki","work","www","zh","zh-cn","zh-tw","zip"],t=Math.floor(3*Math.random()+1),n=e.sort(()=>.5-Math.random()).slice(0,t).join("/");return`/${n}`}()+n:n)+s}&encryption=none${r.skipCertVerify?"&insecure=1&allowInsecure=1":""}#${encodeURIComponent(a)}`):(console.warn(`[subContent] invalidIPIgnored: ${e}`),null)}).filter(e=>null!==e).join("\n")}else{const e=`${r.subConverterConfig.SUBAPI}/sub?target=${w}&url=${encodeURIComponent(o.protocol+"//"+o.host+"/sub?target=mixed&token="+s+(o.searchParams.has("sub")&&""!=o.searchParams.get("sub")?`&sub=${o.searchParams.get("sub")}`:""))}&config=${encodeURIComponent(r.subConverterConfig.SUBCONFIG)}&emoji=${r.subConverterConfig.SUBEMOJI}&scv=${r.skipCertVerify}`;try{const t=await fetch(e,{headers:{"User-Agent":"Subconverter for "+w+" edgetunnel(https://github.com/cmliu/edgetunnel)"}});if(!t.ok)return new Response("subConverterError:"+t.statusText,{status:t.status});b=await t.text(),(o.searchParams.has("surge")||i.includes("surge"))&&(b=function(e,t,n){const s=e.includes("\r\n")?e.split("\r\n"):e.split("\n");let r="";const o=n.enable0RTT?n.PATH+"?ed=2560":n.PATH;for(let e of s)if(!e.includes("= trojan,")||e.includes("ws=true")||e.includes("ws-path="))r+=e+"\n";else{const t=e.split("sni=")[1].split(",")[0],s=`sni=${t}, skip-cert-verify=${n.skipCertVerify}`,a=`sni=${t}, skip-cert-verify=${n.skipCertVerify}, ws=true, ws-path=${o}, ws-headers=Host:"${t}"`;r+=e.replace(new RegExp(s,"g"),a).replace("[","").replace("]","")+"\n"}return r=`#!MANAGED-CONFIG ${t} interval=${60*n.subGenerator.SUBUpdateTime*60} strict=false`+r.substring(r.indexOf("\n")),r}(b,o.protocol+"//"+o.host+"/sub?token="+s+"&surge",r))}catch(e){return new Response("subConverterError:"+e.message,{status:403})}}return i.includes("subconverter")||(b=await function(e,t,n=2){const s=[...t].sort(()=>Math.random()-.5);let r=0,o=null;return e.replace(/example\.com/g,()=>(r%n===0&&(o=function(e){if(!e?.includes("*"))return e;const t="abcdefghijklmnopqrstuvwxyz0123456789";return e.replace(/\*/g,()=>{let e="";for(let n=0;n{if("tun"===e.type){const t=[];e.inet4_address&&t.push(e.inet4_address),e.inet6_address&&t.push(e.inet6_address),t.length>0&&(e.address=t,delete e.inet4_address,delete e.inet6_address);const n=[];Array.isArray(e.inet4_route_address)&&n.push(...e.inet4_route_address),Array.isArray(e.inet6_route_address)&&n.push(...e.inet6_route_address),n.length>0&&(e.route_address=n,delete e.inet4_route_address,delete e.inet6_route_address);const s=[];Array.isArray(e.inet4_route_exclude_address)&&s.push(...e.inet4_route_exclude_address),Array.isArray(e.inet6_route_exclude_address)&&s.push(...e.inet6_route_exclude_address),s.length>0&&(e.route_exclude_address=s,delete e.inet4_route_exclude_address,delete e.inet6_route_exclude_address)}});const o=new Map,a=(e,t=!1)=>{Array.isArray(e)&&e.forEach(e=>{if(e.geosite){const t=Array.isArray(e.geosite)?e.geosite:[e.geosite];e.rule_set=t.map(e=>{const t=`geosite-${e}`;return o.has(t)||o.set(t,{tag:t,type:"remote",format:"binary",url:`https://gh.090227.xyz/https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-${e}.srs`,download_detour:"DIRECT"}),t}),delete e.geosite}if(e.geoip){const t=Array.isArray(e.geoip)?e.geoip:[e.geoip];e.rule_set=e.rule_set||[],t.forEach(t=>{const n=`geoip-${t}`;o.has(n)||o.set(n,{tag:n,type:"remote",format:"binary",url:`https://gh.090227.xyz/https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-${t}.srs`,download_detour:"DIRECT"}),e.rule_set.push(n)}),delete e.geoip}const n=t?"server":"outbound",s=String(e[n]).toUpperCase();"REJECT"!==s&&"BLOCK"!==s||(e.action="reject",e.method="drop",delete e[n])})};r.dns&&r.dns.rules&&a(r.dns.rules,!0),r.route&&r.route.rules&&a(r.route.rules,!1),o.size>0&&(r.route||(r.route={}),r.route.rule_set=Array.from(o.values())),r.outbounds||(r.outbounds=[]),r.outbounds=r.outbounds.filter(e=>"REJECT"!==e.tag&&"block"!==e.tag);const i=new Set(r.outbounds.map(e=>e.tag));if(i.has("DIRECT")||(r.outbounds.push({type:"direct",tag:"DIRECT"}),i.add("DIRECT")),r.dns&&r.dns.servers){const e=new Set(r.dns.servers.map(e=>e.tag));r.dns.rules&&r.dns.rules.forEach(t=>{t.server&&!e.has(t.server)&&("dns_block"===t.server&&e.has("block")?t.server="block":t.server.toLowerCase().includes("block")&&!e.has(t.server)&&(r.dns.servers.push({tag:t.server,address:"rcode://success"}),e.add(t.server)))})}return r.outbounds.forEach(e=>{"selector"!==e.type&&"urltest"!==e.type||Array.isArray(e.outbounds)&&(e.outbounds=e.outbounds.filter(e=>{const t=e.toUpperCase();return i.has(e)&&"REJECT"!==t&&"BLOCK"!==t}),0===e.outbounds.length&&e.outbounds.push("DIRECT"))}),t&&r.outbounds.forEach(e=>{(e.uuid&&e.uuid===t||e.password&&e.password===t)&&(e.tls||(e.tls={enabled:!0}),n&&(e.tls.utls={enabled:!0,fingerprint:n}),s&&(e.tls.ech={enabled:!0,config:`-----BEGIN ECH CONFIGS-----\n${s}\n-----END ECH CONFIGS-----`}))}),JSON.stringify(r,null,2)}catch(t){return console.error("singboxPatchFailed:",t),JSON.stringify(JSON.parse(e),null,2)}}(b,r.UUID,r.Fingerprint,r.ECH?await async function(e){try{const t=await fetch(`https://1.1.1.1/dns-query?name=${encodeURIComponent(e)}&type=65`,{headers:{accept:"application/dns-json"}}),n=await t.json();if(!n.Answer?.length)return"";for(let e of n.Answer){if(65!==e.type||!e.data)continue;const t=e.data.match(/ech=([^\s]+)/);if(t)return t[1].replace(/"/g,"");if(e.data.startsWith("\\#")){const t=e.data.split(" ").slice(2).join(""),n=new Uint8Array(t.match(/.{1,2}/g).map(e=>parseInt(e,16)));let s=2;for(;s0){const e=s.map(e=>` "${e}":\n - tls://8.8.8.8\n - https://doh.cmliussss.com/CMLiussss\n - ${f}`).join("\n");if(/^\s{2}nameserver-policy:\s*(?:\n|$)/m.test(r))r=r.replace(/^(\s{2}nameserver-policy:\s*\n)/m,`$1${e}\n`);else{const t=r.split("\n");let n=-1,s=!1;for(let e=0;e0&&c+1=0;t--)if(n[t].trim()){e=t;break}if(e>=0){const t=" ".repeat(r);n.splice(e+1,0,`${t}ech-opts:`,`${t} enable: true`)}}i.push(...n)}else i.push(e),c++}return i.join("\n")}(b,r.UUID,r.ECH,r.HOSTS),m["content-type"]="application/x-yaml; charset=utf-8"),new Response(b,{status:200,headers:m})}}else if("locations"===s){const t=e.headers.get("Cookie")||"",n=t.split(";").find(e=>e.trim().startsWith("auth="))?.split("=")[1];if(n&&n==await S(a+A+C))return fetch(new Request("https://speed.cloudflare.com/locations",{headers:{Referer:"https://speed.cloudflare.com/"}}))}else if("robots.txt"===s)return new Response("User-agent: *\nDisallow: /",{status:200,headers:{"Content-Type":"text/plain; charset=UTF-8"}})}else if(!_)return fetch(m+"/noKV").then(e=>{const t=new Headers(e.headers);return t.set("Cache-Control","no-store, no-cache, must-revalidate, proxy-revalidate"),t.set("Pragma","no-cache"),t.set("Expires","0"),new Response(e.body,{status:404,statusText:e.statusText,headers:t})})}let F=t.URL||"nginx";if(F&&"nginx"!==F&&"1101"!==F){F=F.trim().replace(/\/$/,""),F.match(/^https?:\/\//i)||(F="https://"+F),F.toLowerCase().startsWith("http://")&&(F="https://"+F.substring(7));try{const e=new URL(F);F=e.protocol+"//"+e.host}catch(e){F="nginx"}}if("1101"===F)return new Response(await async function(e,t){const n=new Date,s=n.getFullYear()+"-"+String(n.getMonth()+1).padStart(2,"0")+"-"+String(n.getDate()).padStart(2,"0")+" "+String(n.getHours()).padStart(2,"0")+":"+String(n.getMinutes()).padStart(2,"0")+":"+String(n.getSeconds()).padStart(2,"0"),r=Array.from(crypto.getRandomValues(new Uint8Array(8))).map(e=>e.toString(16).padStart(2,"0")).join("");return`\n\x3c!--[if lt IE 7]> \x3c!--\x3e \x3c!--\nWorker threw exception | ${e} | Cloudflare\n\n\n\n\n\n\n\x3c!--[if lt IE 9]>body{margin:0;padding:0}\n\n\n\x3c!--[if gte IE 10]>\x3c!--\x3e\n