diff --git a/x27cn/README.md b/x27cn/README.md index bdd2c6a..df1ec70 100644 --- a/x27cn/README.md +++ b/x27cn/README.md @@ -167,9 +167,197 @@ X27CN v2 使用以下加密步骤: 3. **位旋转** - 循环左移 5 位 4. **状态混合** - 使用累积状态值混淆 +## 代码压缩混淆(v1.2.0 新增) + +除了加密型混淆,X27CN 还提供专业的代码压缩和标识符混淆功能。 + +### Python API + +```python +import x27cn + +# 压缩 CSS +minified_css = x27cn.minify_css('body { color: red; }') +# 输出: 'body{color:red}' + +# 压缩 JavaScript(带变量名混淆) +minified_js = x27cn.minify_js('function hello() { var name = "world"; return name; }') +# 输出: 'function hello(){var _a="world";return _a}' + +# 压缩 HTML(自动处理内联 CSS/JS) +minified_html = x27cn.minify_html('
') +# 输出: '' + +# 使用 Node.js 工具(效果更好,需安装 terser/clean-css) +minified = x27cn.minify_js_node(js_code) + +# 压缩文件 +x27cn.minify_file('app.js') # 生成 app.min.js +x27cn.minify_file('style.css', 'dist/style.css') +``` + +### 高级混淆 + +```python +import x27cn + +# 标识符混淆(变量名替换为 _$0, _$1, ...) +obfuscated = x27cn.obfuscate_identifiers(js_code) + +# 添加死代码(增加逆向难度) +obfuscated = x27cn.add_dead_code(js_code, complexity=3) +``` + +### 命令行 + +```bash +# 压缩 JavaScript +x27cn minify app.js + +# 压缩 CSS +x27cn minify style.css dist/style.min.css + +# 不混淆变量名 +x27cn minify app.js --no-mangle + +# 不使用 Node.js 工具(纯 Python) +x27cn minify app.js --no-node + +# 添加死代码 +x27cn minify app.js --dead-code=3 + +# 额外标识符混淆 +x27cn minify app.js --identifiers +``` + +### Node.js 工具支持 + +如果安装了以下 npm 包,X27CN 会自动使用它们以获得更好的压缩效果: + +```bash +npm install -g terser clean-css-cli html-minifier-terser +``` + +如果未安装,会自动降级到纯 Python 实现。 + +### 压缩 vs 加密混淆对比 + +| 特性 | `minify` | `obfuscate` | +|------|----------|-------------| +| 可读性 | 低 | 极低 | +| 代码可执行 | 直接执行 | 需解密 | +| 密钥需求 | 不需要 | 需要 | +| 文件大小 | 显著减小 | 略增大 | +| 安全性 | 中等 | 较高 | +| 性能影响 | 无 | 有解密开销 | + +**推荐使用场景:** +- `minify`: 生产环境部署、减小文件体积、基础代码保护 +- `obfuscate`: 需要更强保护的敏感代码、API密钥保护 + +## 密码安全(v1.3.0 新增) + +X27CN 现在提供安全的密码处理功能,适合用户认证场景。 + +### 密码哈希(存储密码) + +```python +import x27cn + +# 哈希密码(用于存储) +hashed = x27cn.hash_password('mypassword123') +# 输出: '$x27cn$100000$base64salt$base64hash' + +# 验证密码 +if x27cn.verify_password('mypassword123', hashed): + print('登录成功') +else: + print('密码错误') +``` + +### 密码强度检测 + +```python +result = x27cn.check_password_strength('abc123') +print(result['level']) # 'weak' +print(result['score']) # 15 +print(result['suggestions']) # ['添加大写字母', '添加特殊字符'] +``` + +### 生成安全密码 + +```python +# 生成 16 位随机密码 +pwd = x27cn.generate_password(16) + +# 自定义选项 +pwd = x27cn.generate_password( + length=20, + include_upper=True, + include_lower=True, + include_digits=True, + include_special=True, + exclude_ambiguous=True # 排除 0O1lI 等易混淆字符 +) +``` + +### 基于密码的加密 + +```python +# 使用密码加密数据(比 key 更安全) +encrypted = x27cn.encrypt_with_password('敏感数据', 'mypassword') +# 输出:、'
+ html = re.sub(r'', process_script, html, flags=re.IGNORECASE)
+
+ # 压缩内联 CSS
+ def process_style(match):
+ attrs = match.group(1)
+ content = match.group(2)
+ minified = minify_css(content)
+ return f''
+ html = re.sub(r'', process_style, html, flags=re.IGNORECASE)
+ else:
+ # 保护 script 和 style
+ html = re.sub(r'', save_block('script'), html, flags=re.IGNORECASE)
+ html = re.sub(r'', save_block('style'), html, flags=re.IGNORECASE)
+
+ # 移除 HTML 注释
+ html = re.sub(r'', '', html)
+
+ # 压缩空白(但保留单个空格)
+ html = re.sub(r'\s+', ' ', html)
+
+ # 移除标签周围的空白
+ html = re.sub(r'>\s+<', '><', html)
+
+ # 移除属性值周围的引号(可选值)
+ # html = re.sub(r'=\s*"([^"\s]+)"', r'=\1', html)
+
+ # 移除冗余属性
+ html = re.sub(r'\s+type\s*=\s*["\']?text/javascript["\']?', '', html, flags=re.IGNORECASE)
+ html = re.sub(r'\s+type\s*=\s*["\']?text/css["\']?', '', html, flags=re.IGNORECASE)
+
+ # 恢复保护的内容
+ for i, block in enumerate(preserved):
+ html = html.replace(f'__BLOCK_{i}__', block)
+
+ return html.strip()
+
+
+# ============== Node.js 工具调用 ==============
+
+def _check_node() -> bool:
+ """检查 Node.js 是否可用"""
+ return shutil.which('node') is not None
+
+
+def _check_npm_package(package: str) -> bool:
+ """检查 npm 包是否安装"""
+ try:
+ result = subprocess.run(
+ ['npm', 'list', package],
+ capture_output=True,
+ text=True,
+ timeout=10
+ )
+ return result.returncode == 0
+ except:
+ return False
+
+
+def minify_css_node(css: str) -> str:
+ """
+ 使用 clean-css (Node.js) 压缩 CSS
+
+ 如果 Node.js 不可用,自动降级到纯 Python 实现。
+ """
+ if not _check_node():
+ return minify_css(css)
+
+ try:
+ # 写入临时文件
+ import tempfile
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.css', delete=False, encoding='utf-8') as f:
+ f.write(css)
+ temp_path = f.name
+
+ # 调用 cleancss
+ result = subprocess.run(
+ ['npx', 'cleancss', temp_path],
+ capture_output=True,
+ text=True,
+ timeout=30
+ )
+
+ os.unlink(temp_path)
+
+ if result.returncode == 0 and result.stdout:
+ return result.stdout
+ else:
+ return minify_css(css)
+ except Exception:
+ return minify_css(css)
+
+
+def minify_js_node(js: str, mangle: bool = True) -> str:
+ """
+ 使用 Terser (Node.js) 压缩混淆 JavaScript
+
+ 如果 Node.js 不可用,自动降级到纯 Python 实现。
+ """
+ if not _check_node():
+ return minify_js(js, mangle)
+
+ try:
+ import tempfile
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False, encoding='utf-8') as f:
+ f.write(js)
+ temp_path = f.name
+
+ cmd = ['npx', 'terser', temp_path, '--compress', '--format', 'comments=false']
+ if mangle:
+ cmd.append('--mangle')
+
+ result = subprocess.run(
+ cmd,
+ capture_output=True,
+ text=True,
+ timeout=30
+ )
+
+ os.unlink(temp_path)
+
+ if result.returncode == 0 and result.stdout:
+ return result.stdout
+ else:
+ return minify_js(js, mangle)
+ except Exception:
+ return minify_js(js, mangle)
+
+
+def minify_html_node(html: str) -> str:
+ """
+ 使用 html-minifier-terser (Node.js) 压缩 HTML
+
+ 如果 Node.js 不可用,自动降级到纯 Python 实现。
+ """
+ if not _check_node():
+ return minify_html(html)
+
+ try:
+ import tempfile
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False, encoding='utf-8') as f:
+ f.write(html)
+ temp_path = f.name
+
+ result = subprocess.run(
+ ['npx', 'html-minifier-terser',
+ '--collapse-whitespace',
+ '--remove-comments',
+ '--minify-css', 'true',
+ '--minify-js', 'true',
+ temp_path],
+ capture_output=True,
+ text=True,
+ timeout=30
+ )
+
+ os.unlink(temp_path)
+
+ if result.returncode == 0 and result.stdout:
+ return result.stdout
+ else:
+ return minify_html(html)
+ except Exception:
+ return minify_html(html)
+
+
+# ============== 高级混淆功能 ==============
+
+def obfuscate_identifiers(js: str) -> str:
+ """
+ 混淆 JavaScript 标识符(变量名、函数名)
+
+ Args:
+ js: JavaScript 源代码
+
+ Returns:
+ 混淆后的代码
+ """
+ # 保护字符串和正则
+ protected = []
+ def save_protected(match):
+ idx = len(protected)
+ protected.append(match.group(0))
+ return f'__PROT_{idx}__'
+
+ # 保护字符串
+ js = re.sub(r"'(?:[^'\\]|\\.)*'", save_protected, js)
+ js = re.sub(r'"(?:[^"\\]|\\.)*"', save_protected, js)
+ js = re.sub(r'`(?:[^`\\]|\\.)*`', save_protected, js)
+
+ # 保护正则
+ js = re.sub(r'/(?![/*])(?:[^/\\]|\\.)+/[gimsuy]*', save_protected, js)
+
+ # 收集声明的变量和函数
+ declarations = set()
+
+ # var/let/const 声明
+ for match in re.finditer(r'\b(?:var|let|const)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)', js):
+ declarations.add(match.group(1))
+
+ # function 声明
+ for match in re.finditer(r'\bfunction\s+([a-zA-Z_$][a-zA-Z0-9_$]*)', js):
+ declarations.add(match.group(1))
+
+ # 生成混淆名
+ def gen_name(index):
+ # 使用 _$0, _$1, ... 格式
+ return f'_${index:x}'
+
+ # 过滤保留字和内置对象
+ reserved = {
+ 'undefined', 'null', 'true', 'false', 'NaN', 'Infinity',
+ 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function',
+ 'Symbol', 'BigInt', 'Math', 'Date', 'RegExp', 'Error',
+ 'JSON', 'console', 'window', 'document', 'navigator',
+ 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
+ 'Promise', 'async', 'await', 'class', 'extends', 'super',
+ 'this', 'new', 'delete', 'typeof', 'instanceof', 'in',
+ 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break',
+ 'continue', 'return', 'throw', 'try', 'catch', 'finally',
+ 'import', 'export', 'default', 'from', 'as', 'of',
+ 'arguments', 'eval', 'with', 'debugger',
+ 'crypto', 'fetch', 'Response', 'Request', 'Headers', 'URL',
+ 'TextEncoder', 'TextDecoder', 'Uint8Array', 'ArrayBuffer',
+ 'atob', 'btoa', 'encodeURIComponent', 'decodeURIComponent',
+ }
+
+ # 创建映射
+ name_map = {}
+ idx = 0
+ for name in sorted(declarations, key=len, reverse=True):
+ if name not in reserved and len(name) > 1:
+ name_map[name] = gen_name(idx)
+ idx += 1
+
+ # 替换
+ for old_name, new_name in name_map.items():
+ js = re.sub(rf'\b{re.escape(old_name)}\b', new_name, js)
+
+ # 恢复保护的内容
+ for i, p in enumerate(protected):
+ js = js.replace(f'__PROT_{i}__', p)
+
+ return js
+
+
+def add_dead_code(js: str, complexity: int = 2) -> str:
+ """
+ 添加无效代码增加逆向难度
+
+ Args:
+ js: JavaScript 源代码
+ complexity: 复杂度 (1-5)
+
+ Returns:
+ 添加死代码后的代码
+ """
+ dead_code_templates = [
+ 'var _$d0=function(){return Math.random()>2};',
+ 'var _$d1=(function(){var _=[];for(var i=0;i<0;i++)_.push(i);return _})();',
+ 'if(typeof _$d2==="undefined")var _$d2=null;',
+ 'try{if(false)throw new Error()}catch(_$e){}',
+ 'var _$d3=Date.now()%1===2?1:0;',
+ ]
+
+ import random
+ dead_codes = random.sample(dead_code_templates, min(complexity, len(dead_code_templates)))
+
+ # 在代码开头添加
+ return ''.join(dead_codes) + js
+
+
+# ============== 统一接口 ==============
+
+def minify(
+ content: str,
+ content_type: str = 'auto',
+ use_node: bool = True,
+ mangle: bool = True
+) -> str:
+ """
+ 统一的压缩混淆接口
+
+ Args:
+ content: 源代码
+ content_type: 类型 ('html', 'css', 'js', 'auto')
+ use_node: 是否尝试使用 Node.js 工具
+ mangle: 是否混淆变量名(仅 JS)
+
+ Returns:
+ 压缩后的代码
+
+ Example:
+ >>> minify('body { color: red; }', 'css')
+ 'body{color:red}'
+ >>> minify('...', 'html')
+ '...'
+ """
+ # 自动检测类型
+ if content_type == 'auto':
+ content_lower = content.strip().lower()
+ if content_lower.startswith(' str:
+ """
+ 压缩混淆文件
+
+ Args:
+ input_path: 输入文件路径
+ output_path: 输出文件路径(默认添加 .min 后缀)
+ use_node: 是否使用 Node.js 工具
+ mangle: 是否混淆变量名
+
+ Returns:
+ 输出文件路径
+
+ Example:
+ >>> minify_file('app.js')
+ 'app.min.js'
+ >>> minify_file('style.css', 'dist/style.css')
+ 'dist/style.css'
+ """
+ # 读取文件
+ with open(input_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # 检测类型
+ ext = os.path.splitext(input_path)[1].lower()
+ if ext in ['.html', '.htm']:
+ content_type = 'html'
+ elif ext == '.css':
+ content_type = 'css'
+ elif ext == '.js':
+ content_type = 'js'
+ else:
+ content_type = 'auto'
+
+ # 压缩
+ minified = minify(content, content_type, use_node, mangle)
+
+ # 输出路径
+ if output_path is None:
+ base, ext = os.path.splitext(input_path)
+ output_path = f"{base}.min{ext}"
+
+ # 写入
+ with open(output_path, 'w', encoding='utf-8') as f:
+ f.write(minified)
+
+ return output_path
+
+
+# ============== 控制流扁平化 ==============
+
+def flatten_control_flow(js: str, intensity: int = 2) -> str:
+ """
+ 控制流扁平化 - 将顺序执行的代码块打乱为 switch-case 结构
+
+ Args:
+ js: JavaScript 源代码
+ intensity: 扁平化强度 (1-3)
+ 1: 仅扁平化顶层语句
+ 2: 扁平化函数体内语句
+ 3: 递归扁平化所有代码块
+
+ Returns:
+ 扁平化后的 JavaScript 代码
+
+ Example:
+ 原始代码:
+ step1(); step2(); step3();
+
+ 扁平化后:
+ var _$s=[2,1,3,0],_$i=0;
+ while(_$s[_$i]!==0){
+ switch(_$s[_$i++]){
+ case 1:step1();break;
+ case 2:step2();break;
+ case 3:step3();break;
+ }
+ }
+ """
+ import random
+
+ # 保护字符串和正则
+ protected = []
+ def save_protected(match):
+ idx = len(protected)
+ protected.append(match.group(0))
+ return f'__PROT_{idx}__'
+
+ js = re.sub(r"'(?:[^'\\]|\\.)*'", save_protected, js)
+ js = re.sub(r'"(?:[^"\\]|\\.)*"', save_protected, js)
+ js = re.sub(r'`(?:[^`\\]|\\.)*`', save_protected, js)
+ js = re.sub(r'/(?![/*])(?:[^/\\]|\\.)+/[gimsuy]*', save_protected, js)
+
+ def flatten_block(code: str, depth: int = 0) -> str:
+ """扁平化一个代码块"""
+ if depth >= intensity:
+ return code
+
+ # 分割语句(简化版本,处理基本情况)
+ statements = _split_statements(code)
+
+ if len(statements) < 3:
+ return code
+
+ # 过滤空语句
+ statements = [s.strip() for s in statements if s.strip()]
+
+ if len(statements) < 3:
+ return code
+
+ # 生成随机编号
+ indices = list(range(1, len(statements) + 1))
+ random.shuffle(indices)
+ indices.append(0) # 结束标志
+
+ # 生成唯一变量名
+ var_s = f'_$s{depth}'
+ var_i = f'_$i{depth}'
+
+ # 构建 switch 语句
+ cases = []
+ for i, stmt in enumerate(statements):
+ case_num = i + 1
+ # 递归扁平化函数体
+ if intensity >= 2 and 'function' in stmt:
+ stmt = _flatten_function_body(stmt, depth + 1, intensity)
+ cases.append(f'case {case_num}:{stmt};break;')
+
+ # 组装结果
+ order_array = ','.join(str(i) for i in indices)
+ result = f'var {var_s}=[{order_array}],{var_i}=0;'
+ result += f'while({var_s}[{var_i}]!==0){{'
+ result += f'switch({var_s}[{var_i}++]){{'
+ result += ''.join(cases)
+ result += '}}'
+
+ return result
+
+ def _split_statements(code: str) -> list:
+ """分割语句(简化版本)"""
+ statements = []
+ current = ''
+ brace_depth = 0
+ paren_depth = 0
+ bracket_depth = 0
+ in_string = None
+
+ i = 0
+ while i < len(code):
+ c = code[i]
+
+ # 检查字符串
+ if c in '"\'`' and (i == 0 or code[i-1] != '\\'):
+ if in_string is None:
+ in_string = c
+ elif in_string == c:
+ in_string = None
+ current += c
+ i += 1
+ continue
+
+ if in_string:
+ current += c
+ i += 1
+ continue
+
+ # 跟踪括号
+ if c == '{':
+ brace_depth += 1
+ elif c == '}':
+ brace_depth -= 1
+ elif c == '(':
+ paren_depth += 1
+ elif c == ')':
+ paren_depth -= 1
+ elif c == '[':
+ bracket_depth += 1
+ elif c == ']':
+ bracket_depth -= 1
+
+ # 分割点
+ if c == ';' and brace_depth == 0 and paren_depth == 0 and bracket_depth == 0:
+ if current.strip():
+ statements.append(current.strip())
+ current = ''
+ elif c == '}' and brace_depth == 0 and paren_depth == 0 and bracket_depth == 0:
+ current += c
+ if current.strip():
+ statements.append(current.strip())
+ current = ''
+ else:
+ current += c
+
+ i += 1
+
+ if current.strip():
+ statements.append(current.strip())
+
+ return statements
+
+ def _flatten_function_body(func_code: str, depth: int, intensity: int) -> str:
+ """扁平化函数体内容"""
+ # 找到函数体
+ match = re.search(r'(function[^{]*\{)([\s\S]*)(\})\s*$', func_code)
+ if not match:
+ return func_code
+
+ prefix = match.group(1)
+ body = match.group(2)
+ suffix = match.group(3)
+
+ # 扁平化函数体
+ flattened_body = flatten_block(body, depth)
+
+ return prefix + flattened_body + suffix
+
+ # 处理整个代码
+ result = flatten_block(js, 0)
+
+ # 恢复保护的内容
+ for i, p in enumerate(protected):
+ result = result.replace(f'__PROT_{i}__', p)
+
+ return result
+
+
+def flatten_control_flow_safe(js: str) -> str:
+ """
+ 安全的控制流扁平化(仅处理简单函数)
+
+ 此版本更保守,只处理明确可以扁平化的代码块,
+ 避免破坏复杂语法结构。
+
+ Args:
+ js: JavaScript 源代码
+
+ Returns:
+ 扁平化后的代码
+ """
+ import random
+
+ # 保护字符串、正则、模板字符串
+ protected = []
+ def save_protected(match):
+ idx = len(protected)
+ protected.append(match.group(0))
+ return f'__PROT_{idx}__'
+
+ js = re.sub(r"'(?:[^'\\]|\\.)*'", save_protected, js)
+ js = re.sub(r'"(?:[^"\\]|\\.)*"', save_protected, js)
+ js = re.sub(r'`(?:[^`\\]|\\.)*`', save_protected, js)
+
+ # 找到简单的立即执行函数 (IIFE)
+ def process_iife(match):
+ inner = match.group(1)
+ # 分割为简单语句
+ stmts = [s.strip() for s in inner.split(';') if s.strip()]
+
+ if len(stmts) < 3:
+ return match.group(0)
+
+ # 过滤包含控制流语句的代码
+ for stmt in stmts:
+ if any(kw in stmt for kw in ['if', 'for', 'while', 'switch', 'try', 'function']):
+ return match.group(0)
+
+ # 生成扁平化代码
+ indices = list(range(1, len(stmts) + 1))
+ random.shuffle(indices)
+ indices.append(0)
+
+ order = ','.join(str(i) for i in indices)
+ cases = ''.join('case {}:{};break;'.format(i+1, stmts[i]) for i in range(len(stmts)))
+
+ return '(function(){{var _$f=[{}],_$g=0;while(_$f[_$g]!==0){{switch(_$f[_$g++]){{{}}}}}}}})()'.format(order, cases)
+
+ # 处理 IIFE
+ js = re.sub(r'\(function\(\)\{([^{}]*)\}\)\(\)', process_iife, js)
+
+ # 恢复保护的内容
+ for i, p in enumerate(protected):
+ js = js.replace(f'__PROT_{i}__', p)
+
+ return js
+
diff --git a/x27cn/x27cn/password.py b/x27cn/x27cn/password.py
new file mode 100644
index 0000000..545c05a
--- /dev/null
+++ b/x27cn/x27cn/password.py
@@ -0,0 +1,411 @@
+"""
+X27CN 密码加密模块
+
+提供安全的密码哈希和验证功能:
+- PBKDF2-SHA256 密码哈希(适合密码存储)
+- 密码强度检测
+- 基于密码的加密/解密
+"""
+
+import os
+import re
+import hashlib
+import hmac
+import base64
+import secrets
+from typing import Tuple, Optional
+
+
+# ============== 密码哈希 ==============
+
+def hash_password(
+ password: str,
+ salt: Optional[bytes] = None,
+ iterations: int = 100000
+) -> str:
+ """
+ 使用 PBKDF2-SHA256 哈希密码
+
+ Args:
+ password: 明文密码
+ salt: 盐值(可选,默认自动生成16字节)
+ iterations: 迭代次数(默认100000,越高越安全但越慢)
+
+ Returns:
+ 格式化的哈希字符串: $x27cn$iterations$salt$hash
+
+ Example:
+ >>> hashed = hash_password('mypassword123')
+ >>> print(hashed)
+ '$x27cn$100000$abc123...$def456...'
+ """
+ if salt is None:
+ salt = os.urandom(16)
+
+ # PBKDF2-SHA256 派生
+ dk = hashlib.pbkdf2_hmac(
+ 'sha256',
+ password.encode('utf-8'),
+ salt,
+ iterations,
+ dklen=32
+ )
+
+ # 编码为 base64
+ salt_b64 = base64.b64encode(salt).decode('ascii')
+ hash_b64 = base64.b64encode(dk).decode('ascii')
+
+ return f'$x27cn${iterations}${salt_b64}${hash_b64}'
+
+
+def verify_password(password: str, hashed: str) -> bool:
+ """
+ 验证密码是否匹配哈希
+
+ Args:
+ password: 待验证的明文密码
+ hashed: hash_password 返回的哈希字符串
+
+ Returns:
+ 密码是否正确
+
+ Example:
+ >>> hashed = hash_password('mypassword123')
+ >>> verify_password('mypassword123', hashed)
+ True
+ >>> verify_password('wrongpassword', hashed)
+ False
+ """
+ try:
+ parts = hashed.split('$')
+ if len(parts) != 5 or parts[1] != 'x27cn':
+ return False
+
+ iterations = int(parts[2])
+ salt = base64.b64decode(parts[3])
+ expected_hash = base64.b64decode(parts[4])
+
+ # 重新计算
+ dk = hashlib.pbkdf2_hmac(
+ 'sha256',
+ password.encode('utf-8'),
+ salt,
+ iterations,
+ dklen=32
+ )
+
+ # 使用恒定时间比较防止时序攻击
+ return hmac.compare_digest(dk, expected_hash)
+ except Exception:
+ return False
+
+
+# ============== 密码强度 ==============
+
+def check_password_strength(password: str) -> dict:
+ """
+ 检测密码强度
+
+ Args:
+ password: 待检测的密码
+
+ Returns:
+ 包含强度信息的字典:
+ - score: 分数 (0-100)
+ - level: 等级 ('weak', 'fair', 'good', 'strong', 'excellent')
+ - suggestions: 改进建议列表
+
+ Example:
+ >>> result = check_password_strength('abc123')
+ >>> print(result['level'])
+ 'weak'
+ """
+ score = 0
+ suggestions = []
+
+ length = len(password)
+
+ # 长度评分
+ if length >= 16:
+ score += 30
+ elif length >= 12:
+ score += 25
+ elif length >= 8:
+ score += 15
+ elif length >= 6:
+ score += 10
+ else:
+ suggestions.append('密码长度至少需要8个字符')
+
+ # 复杂性评分
+ has_lower = bool(re.search(r'[a-z]', password))
+ has_upper = bool(re.search(r'[A-Z]', password))
+ has_digit = bool(re.search(r'[0-9]', password))
+ has_special = bool(re.search(r'[!@#$%^&*(),.?":{}|<>_\-+=\[\]\\\/`~]', password))
+
+ complexity = sum([has_lower, has_upper, has_digit, has_special])
+ score += complexity * 15
+
+ if not has_lower:
+ suggestions.append('添加小写字母')
+ if not has_upper:
+ suggestions.append('添加大写字母')
+ if not has_digit:
+ suggestions.append('添加数字')
+ if not has_special:
+ suggestions.append('添加特殊字符 (!@#$%^&* 等)')
+
+ # 熵评分(字符多样性)
+ unique_chars = len(set(password))
+ if unique_chars >= 10:
+ score += 10
+ elif unique_chars >= 6:
+ score += 5
+
+ # 常见模式扣分
+ common_patterns = [
+ r'123', r'abc', r'qwerty', r'password', r'admin',
+ r'111', r'000', r'aaa', r'(.)\1{2,}' # 连续重复字符
+ ]
+ for pattern in common_patterns:
+ if re.search(pattern, password.lower()):
+ score -= 10
+ if pattern == r'(.)\1{2,}':
+ suggestions.append('避免连续重复的字符')
+ else:
+ suggestions.append('避免使用常见模式')
+ break
+
+ # 限制分数范围
+ score = max(0, min(100, score))
+
+ # 确定等级
+ if score >= 80:
+ level = 'excellent'
+ elif score >= 60:
+ level = 'strong'
+ elif score >= 40:
+ level = 'good'
+ elif score >= 20:
+ level = 'fair'
+ else:
+ level = 'weak'
+
+ return {
+ 'score': score,
+ 'level': level,
+ 'suggestions': suggestions,
+ 'length': length,
+ 'has_lower': has_lower,
+ 'has_upper': has_upper,
+ 'has_digit': has_digit,
+ 'has_special': has_special,
+ }
+
+
+# ============== 密码生成 ==============
+
+def generate_password(
+ length: int = 16,
+ include_upper: bool = True,
+ include_lower: bool = True,
+ include_digits: bool = True,
+ include_special: bool = True,
+ exclude_ambiguous: bool = True
+) -> str:
+ """
+ 生成安全的随机密码
+
+ Args:
+ length: 密码长度(默认16)
+ include_upper: 包含大写字母
+ include_lower: 包含小写字母
+ include_digits: 包含数字
+ include_special: 包含特殊字符
+ exclude_ambiguous: 排除易混淆字符 (0O1lI)
+
+ Returns:
+ 随机密码
+
+ Example:
+ >>> pwd = generate_password(20)
+ >>> print(len(pwd))
+ 20
+ """
+ chars = ''
+ required = []
+
+ lower_chars = 'abcdefghijkmnopqrstuvwxyz' if exclude_ambiguous else 'abcdefghijklmnopqrstuvwxyz'
+ upper_chars = 'ABCDEFGHJKMNPQRSTUVWXYZ' if exclude_ambiguous else 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ digit_chars = '23456789' if exclude_ambiguous else '0123456789'
+ special_chars = '!@#$%^&*-_=+?'
+
+ if include_lower:
+ chars += lower_chars
+ required.append(secrets.choice(lower_chars))
+ if include_upper:
+ chars += upper_chars
+ required.append(secrets.choice(upper_chars))
+ if include_digits:
+ chars += digit_chars
+ required.append(secrets.choice(digit_chars))
+ if include_special:
+ chars += special_chars
+ required.append(secrets.choice(special_chars))
+
+ if not chars:
+ raise ValueError('至少需要选择一种字符类型')
+
+ # 生成剩余字符
+ remaining = length - len(required)
+ password_chars = required + [secrets.choice(chars) for _ in range(remaining)]
+
+ # 打乱顺序
+ secrets.SystemRandom().shuffle(password_chars)
+
+ return ''.join(password_chars)
+
+
+# ============== 基于密码的加密 ==============
+
+def encrypt_with_password(plaintext: str, password: str) -> str:
+ """
+ 使用密码加密数据
+
+ 使用 PBKDF2 派生加密密钥,然后用 X27CN 算法加密。
+ 输出格式为 标准格式,盐值编码在开头。
+
+ Args:
+ plaintext: 明文数据
+ password: 加密密码
+
+ Returns:
+ 加密后的密文( 格式,包含盐值)
+
+ Example:
+ >>> encrypted = encrypt_with_password('secret data', 'mypassword')
+ >>> # 输出:
+ >>> decrypted = decrypt_with_password(encrypted, 'mypassword')
+ >>> print(decrypted)
+ 'secret data'
+ """
+ from .core import encrypt
+
+ # 生成随机盐(16字节)
+ salt = os.urandom(16)
+
+ # 派生密钥
+ key = hashlib.pbkdf2_hmac(
+ 'sha256',
+ password.encode('utf-8'),
+ salt,
+ 50000,
+ dklen=32
+ )
+ key_str = base64.b64encode(key).decode('ascii')[:32]
+
+ # 加密数据
+ encrypted = encrypt(plaintext, key_str)
+
+ # 将盐值编码为 格式
+ salt_hex = ''.join(f'<{b:02x}>' for b in salt)
+
+ # 添加魔数标识 表示密码加密
+ return f'{salt_hex}{encrypted}'
+
+
+def decrypt_with_password(ciphertext: str, password: str) -> str:
+ """
+ 使用密码解密数据
+
+ Args:
+ ciphertext: encrypt_with_password 返回的密文( 格式)
+ password: 解密密码
+
+ Returns:
+ 解密后的明文
+
+ Raises:
+ ValueError: 密码错误或数据损坏
+
+ Example:
+ >>> encrypted = encrypt_with_password('hello', 'pass123')
+ >>> decrypt_with_password(encrypted, 'pass123')
+ 'hello'
+ """
+ from .core import decrypt
+
+ try:
+ # 检查魔数
+ if not ciphertext.startswith(''):
+ raise ValueError('无效的密文格式(缺少密码加密标识)')
+
+ # 移除魔数
+ data = ciphertext[6:] # 去掉
+
+ # 提取盐值(16字节 = 16个 块 = 64字符)
+ salt_part = data[:64] # 16 * 4 = 64
+ encrypted_data = data[64:]
+
+ # 解析盐值
+ salt_bytes = []
+ import re
+ salt_matches = re.findall(r'<([0-9a-fA-F]{2})>', salt_part)
+ if len(salt_matches) != 16:
+ raise ValueError('无效的盐值格式')
+ salt = bytes(int(h, 16) for h in salt_matches)
+
+ # 派生密钥
+ key = hashlib.pbkdf2_hmac(
+ 'sha256',
+ password.encode('utf-8'),
+ salt,
+ 50000,
+ dklen=32
+ )
+ key_str = base64.b64encode(key).decode('ascii')[:32]
+
+ # 解密
+ return decrypt(encrypted_data, key_str)
+ except ValueError:
+ raise
+ except Exception as e:
+ raise ValueError(f'解密失败(密码可能错误): {e}')
+
+
+# ============== 快速哈希 ==============
+
+def quick_hash(data: str, algorithm: str = 'sha256') -> str:
+ """
+ 快速计算字符串哈希值
+
+ Args:
+ data: 要哈希的数据
+ algorithm: 算法 ('md5', 'sha1', 'sha256', 'sha512')
+
+ Returns:
+ 十六进制哈希值
+
+ Example:
+ >>> quick_hash('hello')
+ '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
+ """
+ h = hashlib.new(algorithm)
+ h.update(data.encode('utf-8'))
+ return h.hexdigest()
+
+
+def md5(data: str) -> str:
+ """计算 MD5 哈希"""
+ return quick_hash(data, 'md5')
+
+
+def sha256(data: str) -> str:
+ """计算 SHA256 哈希"""
+ return quick_hash(data, 'sha256')
+
+
+def sha512(data: str) -> str:
+ """计算 SHA512 哈希"""
+ return quick_hash(data, 'sha512')
+
diff --git a/破皮版workers.js b/破皮版workers.js
index 1eb075a..e276b96 100644
--- a/破皮版workers.js
+++ b/破皮版workers.js
@@ -1 +1 @@
-import{connect as e}from"cloudflare:sockets";var _$d=function(_e,_k){if(!_e)return"";_k=_k||"x27cn2026";var _kb=[];for(var i=0;i<_k.length;i++)_kb.push(_k.charCodeAt(i));var _ek=new Array(256),_sb=new Array(256),_isb=new Array(256);for(var i=0;i<256;i++){_ek[i]=(_kb[i%_kb.length]^((7*i+13)&255))&255;_sb[i]=(167*i+89)&255}for(var i=0;i<256;i++)_isb[_sb[i]]=i;var _h=_e.replace(/<([0-9a-fA-F]{1,4})>/g,"$1");if(_h.length%2!==0)return"";var _eb=[];for(var i=0;i<_h.length;i+=2)_eb.push(parseInt(_h.substr(i,2),16));var _st=0;for(var i=0;i<_kb.length;i++)_st^=_kb[i];var _r=[];for(var i=0;i<_eb.length;i++){var v=_eb[i],_ns=(_st+v+_ek[(i+128)%256])&255;v=(((v-3*i-_st)%256)+256)%256;v=((v>>5)|(v<<3))&255;v=_isb[v];v=v^_ek[i%256];_r.push(v);_st=_ns}try{return decodeURIComponent(_r.map(x=>'%'+x.toString(16).padStart(2,'0')).join(''))}catch(e){return String.fromCharCode(..._r)}};var _$e=function(_t,_k){if(!_t)return"";_k=_k||"x27cn2026";var _kb=(new TextEncoder).encode(_k),_ek=new Uint8Array(256),_sb=new Uint8Array(256);for(var i=0;i<256;i++){_ek[i]=(_kb[i%_kb.length]^((7*i+13)&255))&255;_sb[i]=(167*i+89)&255}var _d=(new TextEncoder).encode(_t),_r=new Uint8Array(_d.length),_st=0;for(var b of _kb)_st^=b;for(var i=0;i<_d.length;i++){var v=_d[i];v=v^_ek[i%256];v=_sb[v];v=((v<<5)|(v>>3))&255;v=(v+3*i+_st)&255;_st=(_st+v+_ek[(i+128)%256])&255;_r[i]=v}var _h=Array.from(_r).map(b=>b.toString(16).padStart(2,"0")).join(""),_o="";for(var i=0;i<_h.length;i+=4)_o+="<"+_h.substr(i,4)+">";return _o};var _$gk=function(_h){var _c="ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789",_k="";for(var i=0;i<8;i++){var _v=parseInt(_h.substr(i*2,2),16);_k+=_c[_v%_c.length]}return _k};var _$n='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 _$m(t){var e=new TextEncoder,n=await crypto.subtle.digest("MD5",e.encode(t)),r=Array.from(new Uint8Array(n)).map(e=>e.toString(16).padStart(2,"0")).join(""),s=await crypto.subtle.digest("MD5",e.encode(r.slice(7,27)));return Array.from(new Uint8Array(s)).map(e=>e.toString(16).padStart(2,"0")).join("").toLowerCase()}async function _$a(t){var e=t.replace(/[\t"'\r\n]+/g,",").replace(/,+/g,",");return","==e.charAt(0)&&(e=e.slice(1)),","==e.charAt(e.length-1)&&(e=e.slice(0,-1)),e.split(",")}let _$c,_$pi="",_$s5=null,_$gp=!1,_$sa="",_$pa={},_$cp,_$ca,_$ci=0,_$ef=!0,_$doh="https://doh.cmliussss.net/CMLiussss",_$wl=["*tapecontent.net","*cloudatacdn.com","*loadshare.org","*cdn-centaurus.com","scholar.google.com"];const _$pg="https://edt-pages.github.io";export default{async fetch(t,n,r){const s=new URL(t.url),o=t.headers.get("User-Agent")||"null",a=t.headers.get("Upgrade"),i=n.ADMIN||n.admin||n.PASSWORD||n.password||n.pswd||n.TOKEN||n.KEY||n.UUID||n.uuid||_$d("<2b63><9361><4453>"),c=n.KEY||_$d("<2b63><93dc><434b><5a78><0ae8><7db7>"),l=await _$m(i+c),u=/^[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}$/,d=n.UUID||n.uuid,p=d&&u.test(d)?d.toLowerCase():[l.slice(0,8),l.slice(8,12),"4"+l.slice(13,16),"8"+l.slice(17,20),l.slice(20)].join("-"),f=n.HOST?(await _$a(n.HOST)).map(e=>e.toLowerCase().replace(/^https?:\/\//,"").split("/")[0].split(":")[0]):[s.hostname],h=f[0];if(n.PROXYIP){const e=await _$a(n.PROXYIP);_$pi=e[Math.floor(Math.random()*e.length)],_$ef=!1}else _$pi=(t.cf.colo+_$d("<4ced><4e64><6a91><8212><9f1b><7714>")).toLowerCase();const g=t.headers.get("X-Real-IP")||t.headers.get("CF-Connecting-IP")||t.headers.get("X-Forwarded-For")||"unknown";if(n.GO2SOCKS5&&(_$wl=await _$a(n.GO2SOCKS5)),_$doh=n.ECH_DOH||n.DOH||_$doh,!a||"websocket"!==a){if("http:"===s.protocol)return Response.redirect(s.href.replace("http://"+s.hostname,"https://"+s.hostname),301);const e=s.pathname.slice(1).toLowerCase(),r=n.TWO_PROXY||n.two_proxy||"",_k=n.ACCESSKEY||n.ACCESS_KEY||n.AKEY||_$gk(l);if(""===e||"/"===e)return new Response(_$n,{status:200,headers:{"Content-Type":"text/html","Server":"nginx/1.18.0"}});if(_$d("<10ef><0af9>")===e){const _u="https://"+s.hostname+"/x2727admin/"+_k,_m=_$d("<3d1a><3222><9f43><4965><7d>")+": "+_k+String.fromCharCode(10,10)+_$d("<9ff4><245c><2362><5a91><0a1e><6354><2867>")+":"+String.fromCharCode(10)+_u+String.fromCharCode(10,10)+_$d("<68d9><4757><2e71><4f03><6e1b><64e7><6b2b><9541><05b0><718c>")+":"+String.fromCharCode(10)+"- "+_$d("<1ed8><7b54><2948><9dac><1a1e><5211><97f1><61d8>")+String.fromCharCode(10)+"- "+_$d("<9d9b><5498><77af><634b><10a8><51c7><7148><4b74><68e6><2007><8825>")+String.fromCharCode(10)+"- "+_$d("<3cc2><4547><491f><0512><8e7c><24b3><1e8f>")+String.fromCharCode(10)+"- "+_$d("<05d8><344e><4b22><6443><06de><4c8e>"),t=_$e(_m);return new Response(t,{headers:{"Content-Type":"text/plain","Access-Control-Allow-Origin":"*","X-Enc":"x27cn","X-Hint":"Decrypt with key: x27cn2026"}})}const _op=s.pathname.slice(1);if(e.startsWith(_$d("<10ef><0af9>")+"/")){const o=_op.substring(11);if(o!==_k){const e=_$d("<9fe9><9c87><1631><26e6><8a41><7e41><4b45><6a18><0071><0969><1ce5><8e32><4699><1f68><3ed9><9050><1446><7c32><61af><40d7><88>");return new Response(_$e(e),{status:403,headers:{"Content-Type":"text/plain","Access-Control-Allow-Origin":"*","X-Enc":"x27cn"}})}const a=t.cf?.colo||"UNKNOWN",i="/"+p+(r?"?two_proxy="+encodeURIComponent(r):""),c="vless://"+p+"@"+s.hostname+":443?security=tls&type=ws&host="+s.hostname+"&sni="+s.hostname+"&path="+encodeURIComponent(i)+"&encryption=none#Node-"+a,l={status:"online",version:"1.8.7",colo:a,host:s.hostname,uuid:p,vless:c,two_proxy:r||null,proxyip:_$pi},u=_$e(JSON.stringify(l),o);return new Response(u,{headers:{"Content-Type":"text/plain","Access-Control-Allow-Origin":"*","X-Enc":"x27cn","X-Key":o}})}return new Response(JSON.stringify({error:"Unknown endpoint",available:["/x2727admin"]}),{status:404,headers:{"Content-Type":"application/json"}})}return new Response(null,{status:101})}};
\ No newline at end of file
+import{connect}from"cloudflare:sockets";function x27cnEncrypt(e,t="x27cn2026"){if(!e)return"";const n=(new TextEncoder).encode(t),s=new Uint8Array(256),r=new Uint8Array(256);for(let e=0;e<256;e++)s[e]=255&(n[e%n.length]^7*e+13&255),r[e]=167*e+89&255;const o=(new TextEncoder).encode(e),a=new Uint8Array(o.length);let i=0;for(const e of n)i^=e;for(let e=0;e>3),t=t+3*e+i&255,i=i+t+s[(e+128)%256]&255,a[e]=t}const c=Array.from(a).map(e=>e.toString(16).padStart(2,"0")).join("");let l="";for(let e=0;e";return l}function generateAccessKey(e){const t="ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";let n="";for(let s=0;s<8;s++){n+=t[parseInt(e.substr(2*s,2),16)%55]}return n}const NGINX_PAGE='\n\n\nWelcome to nginx! \n\n\n\nWelcome to nginx!
\nIf you see this page, the nginx web server is successfully installed and working. Further configuration is required.
\nFor online documentation and support please refer to nginx.org.
\nCommercial support is available at nginx.com.
\nThank you for using nginx.
\n\n';let config_JSON,cacheproxyIP,cachedProxyArray,proxyIP="",enableSocks5Proxy=null,enableGlobalSocks5=!1,mySocks5Account="",parsedSocks5Address={},cachedProxyIndex=0,enableProxyFallback=!0,ECH_DOH="https://doh.cmliussss.net/CMLiussss",socks5Whitelist=["*tapecontent.net","*cloudatacdn.com","*loadshare.org","*cdn-centaurus.com","scholar.google.com"];const pagesStaticUrl="https://edt-pages.github.io";export default{async fetch(e,t,n){const s=new URL(e.url),r=e.headers.get("User-Agent")||"null",o=e.headers.get("Upgrade"),a=t.ADMIN||t.admin||t.PASSWORD||t.password||t.pswd||t.TOKEN||t.KEY||t.UUID||t.uuid||"cfspider-public",i=t.KEY||"cfspider-default-key",c=await MD5MD5(a+i),l=/^[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}$/,d=t.UUID||t.uuid,u=d&&l.test(d)?d.toLowerCase():[c.slice(0,8),c.slice(8,12),"4"+c.slice(13,16),"8"+c.slice(17,20),c.slice(20)].join("-"),p=t.CUSTOM_HOST||t.HOST||e.headers.get("X-Forwarded-Host")||e.headers.get("Host")||s.hostname,f=(t.HOST?(await parseToArray(t.HOST)).map(e=>e.toLowerCase().replace(/^https?:\/\//,"").split("/")[0].split(":")[0]):[s.hostname])[0];if(t.PROXYIP){const e=await parseToArray(t.PROXYIP);proxyIP=e[Math.floor(Math.random()*e.length)],enableProxyFallback=!1}else proxyIP=(e.cf.colo+".PrOxYIp.CmLiUsSsS.nEt").toLowerCase();const h=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&&(socks5Whitelist=await parseToArray(t.GO2SOCKS5)),ECH_DOH=t.ECH_DOH||t.DOH||ECH_DOH,o&&"websocket"===o){if(a){await reversedelegateparamsget(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 handleWebSocket(e,u,r)}}else{if("http:"===s.protocol)return Response.redirect(s.href.replace(`http://${s.hostname}`,`https://${s.hostname}`),301);const o=s.pathname.slice(1).toLowerCase(),g=("false"!==t.NEW_IP&&t.NEW_IP,!d||!l.test(d)),m=t.TWO_PROXY||t.two_proxy||"",y=t.ACCESSKEY||t.ACCESS_KEY||t.AKEY||generateAccessKey(c);if(""===o||"/"===o)return new Response(NGINX_PAGE,{status:200,headers:{"Content-Type":"text/html",Server:"nginx/1.18.0"}});if("x2727admin"===o){const e=x27cnEncrypt(`Your access key: ${y}\n\nFull URL:\n${`https://${p}/x2727admin/${y}`}\n\nEnvironment variables (optional):\n- ACCESSKEY: Custom access key\n- UUID: UUID parameter for cfspider\n- KEY: Custom encryption key\n- PROXYIP: Custom proxy IP`);return new Response(e,{headers:{"Content-Type":"text/plain","Access-Control-Allow-Origin":"*","X-Enc":"x27cn","X-Hint":"Decrypt with key: x27cn2026"}})}if(o.startsWith("x2727admin/")){const t=s.pathname.slice(1).substring(11);if(t!==y)return new Response(x27cnEncrypt("Invalid key. Please visit /x2727admin to get the correct key."),{status:403,headers:{"Content-Type":"text/plain","Access-Control-Allow-Origin":"*","X-Enc":"x27cn"}});const n=e.cf?.colo||"UNKNOWN",r="/"+u+(m?"?two_proxy="+encodeURIComponent(m):""),o={status:"online",version:"1.8.7",colo:n,host:p,uuid:u,vless:`vless://${u}@${p}:443?security=tls&type=ws&host=${p}&sni=${p}&path=${encodeURIComponent(r)}&encryption=none#Node-${n}`,two_proxy:m||null,proxyip:proxyIP},a=x27cnEncrypt(JSON.stringify(o),t);return new Response(a,{headers:{"Content-Type":"text/plain","Access-Control-Allow-Origin":"*","X-Enc":"x27cn","X-Key":t}})}if("status"===o){const t=e.cf?.colo||"UNKNOWN",n="/"+u+(m?"?two_proxy="+encodeURIComponent(m):""),r="vless://"+u+"@"+s.hostname+":443?security=tls&type=ws&host="+s.hostname+"&sni="+s.hostname+"&path="+encodeURIComponent(n)+"&encryption=none#CFspider-"+t;return new Response(JSON.stringify({status:"online",version:"1.8.7",colo:t,host:s.hostname,uuid:u,vless:r,two_proxy:m||null},null,2),{headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}})}if("api/proxyip"===o){const n=e.cf?.colo||"UNKNOWN",s=(n+".proxyip.cmliussss.net").toLowerCase(),r=t.PROXYIP||"";return new Response(JSON.stringify({colo:n,default:s,hk:"proxyip.cfspider.com",env:r||null,current:r||s,options:{default:{name:"Netherlands",address:s},hk:{name:"Hong Kong",address:"proxyip.cfspider.com"}}}),{headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}})}if("api/uuid"===o||"api/config"===o){const e="false"!==t.NEW_IP&&"0"!==t.NEW_IP,n=t.TWO_PROXY||t.two_proxy||"",r={host:s.hostname,new_ip:e,version:"1.8.7",is_default_uuid:g,two_proxy_enabled:!!n};if(g?(r.uuid=u,r.vless_path=n?"/"+u+"?two_proxy="+encodeURIComponent(n):"/"+u):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(JSON.stringify(r),{headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}})}if("proxy"===o||o.startsWith("proxy?")){const n=s.searchParams.get("url"),r=s.searchParams.get("method")||"GET",o=s.searchParams.get("two_proxy");if(!n)return new Response(JSON.stringify({error:"Missing url parameter"}),{status:400,headers:{"Content-Type":"application/json"}});try{const s={};for(const[t,n]of e.headers)if(t.toLowerCase().startsWith("x-cfspider-header-")){s[t.substring(18)]=n}let a;const i=o||t.TWO_PROXY||t.two_proxy||"";if(i){const t=i.split(":"),o=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:f}=await import("cloudflare:sockets");if(p)return new Response(JSON.stringify({error:"HTTPS + two_proxy notSupportedvia /proxy API。pleaseUse Python cfspider.get() with two_proxy params。",hint:"cfspider.get(url, cf_proxies=..., uuid=..., two_proxy=...)",reason:"Workers /proxy API onlysupport HTTP twoProxy"}),{status:501,headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}});{const t=f({hostname:o,port:a}),i=t.writable.getWriter(),d=t.readable.getReader();let p=`${r} ${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(s))p+=`${e}: ${t}\r\n`;p+="Connection: close\r\n\r\n",await i.write((new TextEncoder).encode(p));let h=new Uint8Array(0);for(;;){const{value:e,done:t}=await d.read();if(t)break;const n=new Uint8Array(h.length+e.length);n.set(h),n.set(e,h.length),h=n}const g=(new TextDecoder).decode(h),m=g.indexOf("\r\n\r\n"),y=g.substring(0,m),w=h.slice((new TextEncoder).encode(g.substring(0,m+4)).length),b=y.split("\r\n")[0],S=parseInt(b.split(" ")[1])||200,x=new Headers;return y.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-CFspider-Version","1.8.6"),x.set("X-CFspider-TwoProxy","enabled"),new Response(w,{status:S,headers:x})}}{const t=new Request(n,{method:r,headers:s,body:"GET"!==r&&"HEAD"!==r?e.body:null});a=await fetch(t);const o=new Headers(a.headers);return o.set("Access-Control-Allow-Origin","*"),o.set("X-CF-Colo",e.cf?.colo||"unknown"),o.set("X-CFspider-Version","1.8.6"),new Response(a.body,{status:a.status,headers:o})}}catch(e){return new Response(JSON.stringify({error:e.message}),{status:500,headers:{"Content-Type":"application/json"}})}}if("api/config/new_ip"===o&&"POST"===e.method){const e="false"!==t.NEW_IP&&"0"!==t.NEW_IP;return new Response(JSON.stringify({new_ip:e,message:"pleasevia Cloudflare Dashboard or wrangler.toml set NEW_IP envVar"}),{headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}})}if(!a)return fetch(pagesStaticUrl+"/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 o=s.pathname.slice(1).toLowerCase(),c=s.pathname.slice(1);if(c===i&&"dontmovethisdefaultsecretkey,ifNeededpleasemanuallyviaaddvariableKEYtolinemodify"!==i){const e=new URLSearchParams(s.search);return e.set("token",await MD5MD5(f+u)),new Response("redirectin...",{status:302,headers:{Location:`/sub?${e.toString()}`}})}if("login"===o){const t=e.headers.get("Cookie")||"",n=t.split(";").find(e=>e.trim().startsWith("auth="))?.split("=")[1];if(n==await MD5MD5(r+i+a))return new Response("redirectin...",{status:302,headers:{Location:"/admin"}});if("POST"===e.method){const t=await e.text();if(new URLSearchParams(t).get("password")===a){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 MD5MD5(r+i+a)}; Path=/; Max-Age=86400; HttpOnly`),e}}return fetch(pagesStaticUrl+"/login")}if("admin"===o||o.startsWith("admin/")){const l=e.headers.get("Cookie")||"",d=l.split(";").find(e=>e.trim().startsWith("auth="))?.split("=")[1];if(!d||d!==await MD5MD5(r+i+a))return new Response("redirectin...",{status:302,headers:{Location:"/login"}});if("admin/log.json"===o){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"===c)try{const e=await getCloudflareUsage(s.searchParams.get("Email"),s.searchParams.get("GlobalAPIKey"),s.searchParams.get("AccountID"),s.searchParams.get("APIToken"));return new Response(JSON.stringify(e,null,2),{status:200,headers:{"Content-Type":"application/json"}})}catch(e){const t={msg:"querypleaserequestamountfailed,failedreason:"+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"===c){if(s.searchParams.get("url")){const e=s.searchParams.get("url");try{new URL(e);const t=await pleaserequestpreferredAPI([e],s.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:"verifypreferredAPIfailed,failedreason:"+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"===o){let e;if(s.searchParams.has("socks5"))e=await SOCKS5canuseabilityverify("socks5",s.searchParams.get("socks5"));else{if(!s.searchParams.has("http"))return new Response(JSON.stringify({error:"missingproxyparams"}),{status:400,headers:{"Content-Type":"application/json;charset=utf-8"}});e=await SOCKS5canuseabilityverify("http",s.searchParams.get("http"))}return new Response(JSON.stringify(e,null,2),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}}if(config_JSON=await readconfig_JSON(t,f,u,t.PATH),"admin/init"===o)try{return config_JSON=await readconfig_JSON(t,f,u,t.PATH,!0),n.waitUntil(pleaserequestlogrecord(t,e,h,"Init_Config",config_JSON)),config_JSON.init="configalreadyheavysetTodefaultvalue",new Response(JSON.stringify(config_JSON,null,2),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){const t={msg:"configresetfailed,failedreason:"+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"===o)try{const s=await e.json();return s.UUID&&s.HOST?(await t.KV.put("config.json",JSON.stringify(s,null,2)),n.waitUntil(pleaserequestlogrecord(t,e,h,"Save_Config",config_JSON)),new Response(JSON.stringify({success:!0,message:"configalreadykeepstore"}),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})):new Response(JSON.stringify({error:"confignotcomplete"}),{status:400,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){return console.error("keepstoreconfigfailed:",e),new Response(JSON.stringify({error:"keepstoreconfigfailed: "+e.message}),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}else if("admin/cf.json"===o)try{const s=await e.json(),r={Email:null,GlobalAPIKey:null,AccountID:null,APIToken:null,UsageAPI:null};if(!s.init||!0!==s.init)if(s.Email&&s.GlobalAPIKey)r.Email=s.Email,r.GlobalAPIKey=s.GlobalAPIKey;else if(s.AccountID&&s.APIToken)r.AccountID=s.AccountID,r.APIToken=s.APIToken;else{if(!s.UsageAPI)return new Response(JSON.stringify({error:"confignotcomplete"}),{status:400,headers:{"Content-Type":"application/json;charset=utf-8"}});r.UsageAPI=s.UsageAPI}return await t.KV.put("cf.json",JSON.stringify(r,null,2)),n.waitUntil(pleaserequestlogrecord(t,e,h,"Save_Config",config_JSON)),new Response(JSON.stringify({success:!0,message:"configalreadykeepstore"}),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){return console.error("keepstoreconfigfailed:",e),new Response(JSON.stringify({error:"keepstoreconfigfailed: "+e.message}),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}else if("admin/tg.json"===o)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:"confignotcomplete"}),{status:400,headers:{"Content-Type":"application/json;charset=utf-8"}});await t.KV.put("tg.json",JSON.stringify(s,null,2))}return n.waitUntil(pleaserequestlogrecord(t,e,h,"Save_Config",config_JSON)),new Response(JSON.stringify({success:!0,message:"configalreadykeepstore"}),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){return console.error("keepstoreconfigfailed:",e),new Response(JSON.stringify({error:"keepstoreconfigfailed: "+e.message}),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}else{if("admin/ADD.txt"!==c)return new Response(JSON.stringify({error:"notSupportedPOSTpleaserequestpath"}),{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(pleaserequestlogrecord(t,e,h,"Save_Custom_IPs",config_JSON)),new Response(JSON.stringify({success:!0,message:"selffixedmeaningIPalreadykeepstore"}),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}catch(e){return console.error("keepstoreselffixedmeaningIPfailed:",e),new Response(JSON.stringify({error:"keepstoreselffixedmeaningIPfailed: "+e.message}),{status:500,headers:{"Content-Type":"application/json;charset=utf-8"}})}}else{if("admin/config.json"===o)return new Response(JSON.stringify(config_JSON,null,2),{status:200,headers:{"Content-Type":"application/json"}});if("admin/ADD.txt"===c){let n=await t.KV.get("ADD.txt")||"null";return"null"==n&&(n=(await generaterandomIP(e,config_JSON.preferredsubscribegenerate.localIPlib.randomnumberamount,config_JSON.preferredsubscribegenerate.localIPlib.pointfixedport))[1]),new Response(n,{status:200,headers:{"Content-Type":"text/plain;charset=utf-8",asn:e.cf.asn}})}if("admin/cf.json"===o)return new Response(JSON.stringify(e.cf,null,2),{status:200,headers:{"Content-Type":"application/json;charset=utf-8"}})}return n.waitUntil(pleaserequestlogrecord(t,e,h,"Admin_Login",config_JSON)),fetch(pagesStaticUrl+"/admin")}if("logout"===o||l.test(o)){const e=new Response("redirectin...",{status:302,headers:{Location:"/login"}});return e.headers.set("Set-Cookie","auth=; Path=/; Max-Age=0; HttpOnly"),e}if("sub"===o){const o=await MD5MD5(f+u);if(s.searchParams.get("token")===o){config_JSON=await readconfig_JSON(t,f,u,t.PATH),n.waitUntil(pleaserequestlogrecord(t,e,h,"Get_SUB",config_JSON));const a=r.toLowerCase(),i=4102329600,c=Date.now(),l=new Date(c);l.setHours(0,0,0,0);const d=Math.floor((c-l.getTime())/864e5*24*1099511627776/2);let p=d,g=d,m=26388279066624;config_JSON.CF.Usage.success&&(p=config_JSON.CF.Usage.pages,g=config_JSON.CF.Usage.workers,m=Number.isFinite(config_JSON.CF.Usage.max)?config_JSON.CF.Usage.max/1e3*1024:102400);const y={"content-type":"text/plain; charset=utf-8","Profile-Update-Interval":config_JSON.preferredsubscribegenerate.SUBUpdateTime,"Profile-web-page-url":s.protocol+"//"+s.host+"/admin","Subscription-Userinfo":`upload=${p}; download=${g}; total=${m}; expire=${i}`,"Cache-Control":"no-store"},w=s.searchParams.has("b64")||s.searchParams.has("base64")||e.headers.get("subconverter-request")||e.headers.get("subconverter-version")||a.includes("subconverter")||a.includes("CF-Workers-SUB".toLowerCase())?"mixed":s.searchParams.has("target")?s.searchParams.get("target"):s.searchParams.has("clash")||a.includes("clash")||a.includes("meta")||a.includes("mihomo")?"clash":s.searchParams.has("sb")||s.searchParams.has("singbox")||a.includes("singbox")||a.includes("sing-box")?"singbox":s.searchParams.has("surge")||a.includes("surge")?"surge&ver=4":s.searchParams.has("quanx")||a.includes("quantumult")?"quanx":s.searchParams.has("loon")||a.includes("loon")?"loon":"mixed";a.includes("mozilla")||(y["Content-Disposition"]=`attachment; filename*=utf-8''${encodeURIComponent(config_JSON.preferredsubscribegenerate.SUBNAME)}`);const b=s.searchParams.has("surge")||a.includes("surge")?"trojan":config_JSON.protocoltype;let S="";if("mixed"===w){const n=config_JSON.startuse0RTT?config_JSON.PATH+"?ed=2560":config_JSON.PATH,r="Shadowrocket"==config_JSON.TLSsplitslice?`&fragment=${encodeURIComponent("1,40-60,30-50,tlshello")}`:"Happ"==config_JSON.TLSsplitslice?`&fragment=${encodeURIComponent("3,1,tlshello")}`:"";let o=[],a="";if(!s.searchParams.has("sub")&&config_JSON.preferredsubscribegenerate.local){const n=config_JSON.preferredsubscribegenerate.localIPlib.randomIP?(await generaterandomIP(e,config_JSON.preferredsubscribegenerate.localIPlib.randomnumberamount,config_JSON.preferredsubscribegenerate.localIPlib.pointfixedport))[0]:await t.KV.get("ADD.txt")?await parseToArray(await t.KV.get("ADD.txt")):(await generaterandomIP(e,config_JSON.preferredsubscribegenerate.localIPlib.randomnumberamount,config_JSON.preferredsubscribegenerate.localIPlib.pointfixedport))[0],s=[],r=[],i=[];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("#");i.push(t[0]+"#"+encodeURIComponent(decodeURIComponent(t[1])))}else i.push(e);else r.push(e);const c=await pleaserequestpreferredAPI(s),l=[...new Set(i.concat(c[1]))];a=l.length>0?l.join("\n")+"\n":"";const d=c[0];o=[...new Set(r.concat(d))]}else{let e=s.searchParams.get("sub")||config_JSON.preferredsubscribegenerate.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("preferredsubscribegeneratehandlerexception:"+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])),o.push(n+s)}}else a+=e+"\n"}catch(e){return new Response("preferredsubscribegeneratehandlerexception:"+e.message,{status:403})}}const i=config_JSON.ECH?`&ech=${encodeURIComponent("cloudflare-ech.com+"+ECH_DOH)}`:"";S=a+o.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 s,o,a="443";return t?(s=t[1],a=t[2]||"443",o=t[3]||s,`${b}://00000000-0000-4000-8000-000000000000@${s}:${a}?security=tls&type=${config_JSON.transmittransferprotocol+i}&host=example.com&fp=${config_JSON.Fingerprint}&sni=example.com&path=${encodeURIComponent(config_JSON.randompath?randompath()+n:n)+r}&encryption=none${config_JSON.jumppasscertificateverify?"&insecure=1&allowInsecure=1":""}#${encodeURIComponent(o)}`):(console.warn(`[subscribecontent] notstandardIPformatalreadyignore: ${e}`),null)}).filter(e=>null!==e).join("\n")}else{const e=`${config_JSON.subscribeconvertconfig.SUBAPI}/sub?target=${w}&url=${encodeURIComponent(s.protocol+"//"+s.host+"/sub?target=mixed&token="+o+(s.searchParams.has("sub")&&""!=s.searchParams.get("sub")?`&sub=${s.searchParams.get("sub")}`:""))}&config=${encodeURIComponent(config_JSON.subscribeconvertconfig.SUBCONFIG)}&emoji=${config_JSON.subscribeconvertconfig.SUBEMOJI}&scv=${config_JSON.jumppasscertificateverify}`;try{const t=await fetch(e,{headers:{"User-Agent":"Subconverter for "+w+" edgetunnel(https://github.com/cmliu/edgetunnel)"}});if(!t.ok)return new Response("subscribeconvertbackendexception:"+t.statusText,{status:t.status});S=await t.text(),(s.searchParams.has("surge")||a.includes("surge"))&&(S=Surgesubscribeconfigfilehotpatch(S,s.protocol+"//"+s.host+"/sub?token="+o+"&surge",config_JSON))}catch(e){return new Response("subscribeconvertbackendexception:"+e.message,{status:403})}}return a.includes("subconverter")||(S=await batchamountreplacechangedomain(S.replace(/00000000-0000-4000-8000-000000000000/g,config_JSON.UUID),config_JSON.HOSTS)),"mixed"!==w||a.includes("mozilla")&&!s.searchParams.has("b64")&&!s.searchParams.has("base64")||(S=btoa(S)),"singbox"===w?(S=Singboxsubscribeconfigfilehotpatch(S,config_JSON.UUID,config_JSON.Fingerprint,config_JSON.ECH?await getECH(f):null),y["content-type"]="application/json; charset=utf-8"):"clash"===w&&(S=Clashsubscribeconfigfilehotpatch(S,config_JSON.UUID,config_JSON.ECH,config_JSON.HOSTS),y["content-type"]="application/x-yaml; charset=utf-8"),new Response(S,{status:200,headers:y})}}else if("locations"===o){const t=e.headers.get("Cookie")||"",n=t.split(";").find(e=>e.trim().startsWith("auth="))?.split("=")[1];if(n&&n==await MD5MD5(r+i+a))return fetch(new Request("https://speed.cloudflare.com/locations",{headers:{Referer:"https://speed.cloudflare.com/"}}))}else if("robots.txt"===o)return new Response("User-agent: *\nDisallow: /",{status:200,headers:{"Content-Type":"text/plain; charset=UTF-8"}})}}let g=t.URL||"nginx";if(g&&"nginx"!==g&&"1101"!==g){g=g.trim().replace(/\/$/,""),g.match(/^https?:\/\//i)||(g="https://"+g),g.toLowerCase().startsWith("http://")&&(g="https://"+g.substring(7));try{const e=new URL(g);g=e.protocol+"//"+e.host}catch(e){g="nginx"}}if("1101"===g)return new Response(await html1101(s.host,h),{status:200,headers:{"Content-Type":"text/html; charset=UTF-8"}});try{const t=new URL(g),n=new Headers(e.headers);n.set("Host",t.host),n.set("Referer",t.origin),n.set("Origin",t.origin),!n.has("User-Agent")&&r&&"null"!==r&&n.set("User-Agent",r);const o=await fetch(t.origin+s.pathname+s.search,{method:e.method,headers:n,body:e.body,cf:e.cf}),a=o.headers.get("content-type")||"";if(/text|javascript|json|xml/.test(a)){const e=(await o.text()).replaceAll(t.host,s.host);return new Response(e,{status:o.status,headers:{...Object.fromEntries(o.headers),"Cache-Control":"no-store"}})}return o}catch(e){}return new Response(await nginx(),{status:200,headers:{"Content-Type":"text/html; charset=UTF-8"}})}};async function handleWebSocket(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=makeReadableStr(o,c);let d=null,u=null;if(n){const e=n.split(":");e.length>=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 forwardataudp(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}=parsetrojanpleaserequest(e,t);if(isSpeedTestSite(s))throw new Error("Speedtest site is blocked");await forwardataTCP(s,n,r,o,null,a,t,u)}else{const{port:n,hostname:s,rawIndex:r,version:c,isUDP:l}=parsevlesspleaserequest(e,t);if(isSpeedTestSite(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 forwardataudp(p,o,d);await forwardataTCP(s,n,p,o,d,a,t,u)}}})).catch(e=>{}),new Response(null,{status:101,webSocket:r})}function parsetrojanpleaserequest(e,t){const n=sha224(t);if(e.byteLength<56)return{hasError:!0,message:"invalid data"};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,56))!==n)return{hasError:!0,message:"invalid password"};const s=e.slice(58);if(s.byteLength<6)return{hasError:!0,message:"invalid S5 request data"};const r=new DataView(s);if(1!==r.getUint8(0))return{hasError:!0,message:"unsupported command, only TCP is allowed"};const o=r.getUint8(1);let a=0,i=2,c="";switch(o){case 1:a=4,c=new Uint8Array(s.slice(i,i+a)).join(".");break;case 3:a=new Uint8Array(s.slice(i,i+1))[0],i+=1,c=(new TextDecoder).decode(s.slice(i,i+a));break;case 4:a=16;const e=new DataView(s.slice(i,i+a)),t=[];for(let n=0;n<8;n++)t.push(e.getUint16(2*n).toString(16));c=t.join(":");break;default:return{hasError:!0,message:`invalid addressType is ${o}`}}if(!c)return{hasError:!0,message:`address is empty, addressType is ${o}`};const l=i+a,d=s.slice(l,l+2);return{hasError:!1,addressType:o,port:new DataView(d).getUint16(0),hostname:c,rawClientData:s.slice(l+4)}}function parsevlesspleaserequest(e,t){if(e.byteLength<24)return{hasError:!0,message:"Invalid data"};const n=new Uint8Array(e.slice(0,1));if(formatIdentifier(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}`}}async function forwardataTCP(e,t,n,s,r,o,a,i=null){async function c(e,t,n,r=null,o=!0){let a;if(r&&r.length>0)for(let e=0;esetTimeout(()=>t(new Error("connectiontimeout")),1e3))]);const e=a.writable.getWriter();return await e.write(n),e.releaseLock(),console.log(`[reversedelegateconnection] successconnectionto: ${s}:${o}`),cachedProxyIndex=t,a}catch(e){console.log(`[reversedelegateconnection] connectionfailed: ${s}:${o}, error: ${e.message}`);try{a?.close?.()}catch(e){}continue}}if(o){a=connect({hostname:e,port:t});const s=a.writable.getWriter();return await s.write(n),s.releaseLock(),a}throw closeSocketQuietly(s),new Error("[reversedelegateconnection] allhavereversedelegateconnectionfailed,andnotenableProxyFallback,connectionterminate。")}async function l(){let i;if("socks5"===enableSocks5Proxy)console.log(`[SOCKS5proxy] proxyto: ${e}:${t}`),i=await socks5Connect(e,t,n);else if("http"===enableSocks5Proxy||"https"===enableSocks5Proxy)console.log(`[HTTPproxy] proxyto: ${e}:${t}`),i=await httpConnect(e,t,n);else{console.log(`[reversedelegateconnection] proxyto: ${e}:${t}`);const s=await parseplaceaddressport(proxyIP,e,a);i=await c(atob("UFJPWFlJUC50cDEuMDkwMjI3Lnh5eg=="),1,n,s,enableProxyFallback)}o.socket=i,i.closed.catch(()=>{}).finally(()=>closeSocketQuietly(s)),connectStreams(i,s,r,null)}console.log(`[TCPturnsend] target: ${e}:${t} | proxyIP: ${proxyIP} | twoProxy: ${i?i.hostname+":"+i.port:"no"} | reversedelegatetype: ${enableSocks5Proxy||"proxyip"}`);if(i){console.log("[TCPturnsend] makeusetwoProxy");try{const a=await async function(){if(!i)throw new Error("twoProxynotconfig");console.log(`[twoProxy] via ${i.hostname}:${i.port} connectionto ${e}:${t}`);const s=connect({hostname:i.hostname,port:i.port}),r=s.writable.getWriter(),o=s.readable.getReader();try{const a=i.username&&i.password?`Proxy-Authorization: Basic ${btoa(`${i.username}:${i.password}`)}\r\n`:"",c=`CONNECT ${e}:${t} HTTP/1.1\r\nHost: ${e}:${t}\r\n${a}User-Agent: CFspider/1.8.3\r\nConnection: keep-alive\r\n\r\n`;await r.write((new TextEncoder).encode(c));let l=new Uint8Array(0),d=-1,u=0;for(;-1===d&&u<8192;){const{done:e,value:t}=await o.read();if(e)throw new Error("proxyconnectionclose");l=new Uint8Array([...l,...t]),u=l.length;for(let e=0;e=300)throw new Error(`proxyconnectionfailed: HTTP ${f}`);return console.log(`[twoProxy] tunnelestablishsuccess: ${e}:${t}`),await r.write(n),r.releaseLock(),o.releaseLock(),s}catch(e){try{r.releaseLock()}catch(e){}try{o.releaseLock()}catch(e){}try{s.close()}catch(e){}throw e}}();o.socket=a,a.closed.catch(()=>{}).finally(()=>closeSocketQuietly(s)),connectStreams(a,s,r,null)}catch(e){console.log(`[twoProxy] connectionfailed: ${e.message}, backexittodefaultmethodstyle`);try{await l()}catch(e){throw e}}}else if(enableSocks5Proxy&&(enableGlobalSocks5||(d=e,socks5Whitelist.some(e=>new RegExp(`^${e.replace(/\*/g,".*")}$`,"i").test(d))))){console.log("[TCPturnsend] startuse SOCKS5/HTTP globalproxy");try{await l()}catch(e){throw e}}else try{console.log(`[TCPturnsend] trydirectto: ${e}:${t}`);const a=await c(e,t,n);o.socket=a,connectStreams(a,s,r,l)}catch(e){await l()}var d}async function forwardataudp(e,t,n){try{const s=connect({hostname:"8.8.4.4",port:53});let r=n;const o=s.writable.getWriter();await o.write(e),o.releaseLock(),await s.readable.pipeTo(new WritableStream({async write(e){if(t.readyState===WebSocket.OPEN)if(r){const n=new Uint8Array(r.length+e.byteLength);n.set(r,0),n.set(e,r.length),t.send(n.buffer),r=null}else t.send(e)}}))}catch(e){}}function closeSocketQuietly(e){try{e.readyState!==WebSocket.OPEN&&e.readyState!==WebSocket.CLOSING||e.close()}catch(e){}}function formatIdentifier(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)}`}async function connectStreams(e,t,n,s){let r=n,o=!1;await e.readable.pipeTo(new WritableStream({async write(e,n){if(o=!0,t.readyState!==WebSocket.OPEN&&n.error("ws.readyState is not open"),r){const n=new Uint8Array(r.length+e.byteLength);n.set(r,0),n.set(e,r.length),t.send(n.buffer),r=null}else t.send(e)},abort(){}})).catch(e=>{closeSocketQuietly(t)}),!o&&s&&await s()}function makeReadableStr(e,t){let n=!1;return new ReadableStream({start(s){e.addEventListener("message",e=>{n||s.enqueue(e.data)}),e.addEventListener("close",()=>{n||(closeSocketQuietly(e),s.close())}),e.addEventListener("error",e=>s.error(e));const{earlyData:r,error:o}=base64ToArray(t);o?s.error(o):r&&s.enqueue(r)},cancel(){n=!0,closeSocketQuietly(e)}})}function isSpeedTestSite(e){const t=[atob("c3BlZWQuY2xvdWRmbGFyZS5jb20=")];if(t.includes(e))return!0;for(const n of t)if(e.endsWith("."+n)||e===n)return!0;return!1}function base64ToArray(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>8,255&t]);if(await c.write(p),a=await l.read(),a.done||0!==new Uint8Array(a.value)[1])throw new Error("S5 connection failed");return await c.write(n),c.releaseLock(),l.releaseLock(),i}catch(e){try{c.releaseLock()}catch(e){}try{l.releaseLock()}catch(e){}try{i.close()}catch(e){}throw e}}async function httpConnect(e,t,n){const{username:s,password:r,hostname:o,port:a}=parsedSocks5Address,i=connect({hostname:o,port:a}),c=i.writable.getWriter(),l=i.readable.getReader();try{const o=`CONNECT ${e}:${t} HTTP/1.1\r\nHost: ${e}:${t}\r\n${s&&r?`Proxy-Authorization: Basic ${btoa(`${s}:${r}`)}\r\n`:""}User-Agent: Mozilla/5.0\r\nConnection: keep-alive\r\n\r\n`;await c.write((new TextEncoder).encode(o));let a=new Uint8Array(0),d=-1,u=0;for(;-1===d&&u<8192;){const{done:e,value:t}=await l.read();if(e)throw new Error("Connection closed before receiving HTTP response");a=new Uint8Array([...a,...t]),u=a.length;const n=a.findIndex((e,t)=>t=300)throw new Error(`Connection failed: HTTP ${p}`);return await c.write(n),c.releaseLock(),l.releaseLock(),i}catch(e){try{c.releaseLock()}catch(e){}try{l.releaseLock()}catch(e){}try{i.close()}catch(e){}throw e}}function Clashsubscribeconfigfilehotpatch(e,t=null,n=!1,s=[]){let r=e.replace(/mode:\s*Rule\b/g,"mode: rule");if(/^dns:\s*(?:\n|$)/m.test(r)||(r="dns:\n enable: true\n default-nameserver:\n - 223.5.5.5\n - 119.29.29.29\n - 114.114.114.114\n use-hosts: true\n nameserver:\n - https://sm2.doh.pub/dns-query\n - https://dns.alidns.com/dns-query\n fallback:\n - 8.8.4.4\n - 101.101.101.101\n - 208.67.220.220\n fallback-filter:\n geoip: true\n domain: [+.google.com, +.facebook.com, +.youtube.com]\n ipcidr:\n - 240.0.0.0/4\n - 0.0.0.0/32\n geoip-code: CN\n"+r),n&&s.length>0){const e=s.map(e=>` "${e}":\n - tls://8.8.8.8\n - https://doh.cmliussss.com/CMLiussss\n - ${ECH_DOH}`).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&&i+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`)}}a.push(...n)}else a.push(e),i++}return a.join("\n")}function Singboxsubscribeconfigfilehotpatch(e,t=null,n="chrome",s=null){try{let r=JSON.parse(e);Array.isArray(r.inbounds)&&r.inbounds.forEach(e=>{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("Singboxhotpatchexecutelinefailed:",t),JSON.stringify(JSON.parse(e),null,2)}}function Surgesubscribeconfigfilehotpatch(e,t,n){const s=e.includes("\r\n")?e.split("\r\n"):e.split("\n");let r="";const o=n.startuse0RTT?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.jumppasscertificateverify}`,a=`sni=${t}, skip-cert-verify=${n.jumppasscertificateverify}, 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.preferredsubscribegenerate.SUBUpdateTime*60} strict=false`+r.substring(r.indexOf("\n")),r}async function pleaserequestlogrecord(e,t,n,s="Get_SUB",r){try{const o=new Date,a={TYPE:s,IP:n,ASN:`AS${t.cf.asn||"0"} ${t.cf.asOrganization||"Unknown"}`,CC:`${t.cf.country||"N/A"} ${t.cf.city||"N/A"}`,URL:t.url,UA:t.headers.get("User-Agent")||"Unknown",TIME:o.getTime()};let i=[];const c=await e.KV.get("log.json");if(c)try{if(i=JSON.parse(c),Array.isArray(i))if("Get_SUB"!==s){const e=o.getTime()-18e5;if(i.some(s=>"Get_SUB"!==s.TYPE&&s.IP===n&&s.URL===t.url&&s.UA===(t.headers.get("User-Agent")||"Unknown")&&s.TIME>=e))return;for(i.push(a);JSON.stringify(i,null,2).length>4194304&&i.length>0;)i.shift()}else for(i.push(a);JSON.stringify(i,null,2).length>4194304&&i.length>0;)i.shift();else i=[a];if(r.TG.startuse)try{const t=await e.KV.get("tg.json"),n=JSON.parse(t);await sendMessage(n.BotToken,n.ChatID,a,r)}catch(e){console.error(`readtg.jsonouterror: ${e.message}`)}}catch(e){i=[a]}else i=[a];await e.KV.put("log.json",JSON.stringify(i,null,2))}catch(e){console.error(`logrecordfailed: ${e.message}`)}}async function sendMessage(e,t,n,s){if(e&&t)try{const r=new Date(n.TIME).toLocaleString("zh-CN",{timeZone:"Asia/Shanghai"}),o=new URL(n.URL),a=`#${s.preferredsubscribegenerate.SUBNAME} logthroughknow\n\n📌 type:#${n.TYPE}\n🌐 IP:${n.IP}\n📍 bitset:${n.CC}\n🏢 ASN:${n.ASN}\n🔗 domain:${o.host}\n🔍 path:${o.pathname+o.search}\n🤖 UA:${n.UA}\n📅 time:${r}\n`+(s.CF.Usage.success?`📊 pleaserequestuseamount:${s.CF.Usage.total}/100000 ${(s.CF.Usage.total/1e5*100).toFixed(2)}%\n`:""),i=`https://api.telegram.org/bot${e}/sendMessage?chat_id=${t}&parse_mode=HTML&text=${encodeURIComponent(a)}`;return fetch(i,{method:"GET",headers:{Accept:"text/html,application/xhtml+xml,application/xml;","Accept-Encoding":"gzip, deflate, br","User-Agent":n.UA||"Unknown"}})}catch(e){console.error("Error sending message:",e)}}function maskcodesensitiveinfo(e,t=3,n=2){if(!e||"string"!=typeof e)return e;if(e.length<=t+n)return e;const s=e.slice(0,t),r=e.slice(-n),o=e.length-t-n;return`${s}${"*".repeat(o)}${r}`}async function MD5MD5(e){const t=new TextEncoder,n=await crypto.subtle.digest("MD5",t.encode(e)),s=Array.from(new Uint8Array(n)).map(e=>e.toString(16).padStart(2,"0")).join(""),r=await crypto.subtle.digest("MD5",t.encode(s.slice(7,27)));return Array.from(new Uint8Array(r)).map(e=>e.toString(16).padStart(2,"0")).join("").toLowerCase()}function randompath(){const e=Math.floor(3*Math.random()+1);return`/${["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"].sort(()=>.5-Math.random()).slice(0,e).join("/")}`}function randomreplacechangethroughconfigsymbol(e){if(!e?.includes("*"))return e;return e.replace(/\*/g,()=>{let e="";for(let t=0;tMath.random()-.5);let r=0,o=null;return e.replace(/example\.com/g,()=>(r%n===0&&(o=randomreplacechangethroughconfigsymbol(s[Math.floor(r/n)%s.length])),r++,o))}async function getECH(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(;se.toLowerCase().replace(/^https?:\/\//,"").split("/")[0].split(":")[0])),config_JSON.UUID=n,config_JSON.PATH=s?s.startsWith("/")?s:"/"+s:config_JSON.reversedelegate.SOCKS5.startuse?"/"+config_JSON.reversedelegate.SOCKS5.startuse+(config_JSON.reversedelegate.SOCKS5.global?"://":"=")+config_JSON.reversedelegate.SOCKS5.accountid:"auto"===config_JSON.reversedelegate.PROXYIP?"/":`/proxyip=${config_JSON.reversedelegate.PROXYIP}`;const c="Shadowrocket"==config_JSON.TLSsplitslice?`&fragment=${encodeURIComponent("1,40-60,30-50,tlshello")}`:"Happ"==config_JSON.TLSsplitslice?`&fragment=${encodeURIComponent("3,1,tlshello")}`:"";config_JSON.Fingerprint||(config_JSON.Fingerprint="chrome"),config_JSON.ECH?config_JSON.preferredsubscribegenerate.SUBUpdateTime=1:config_JSON.ECH=!1;const l=config_JSON.ECH?`&ech=${encodeURIComponent("cloudflare-ech.com+"+ECH_DOH)}`:"";config_JSON.LINK=`${config_JSON.protocoltype}://${n}@${o}:443?security=tls&type=${config_JSON.transmittransferprotocol+l}&host=${o}&fp=${config_JSON.Fingerprint}&sni=${o}&path=${encodeURIComponent(config_JSON.startuse0RTT?config_JSON.PATH+"?ed=2560":config_JSON.PATH)+c}&encryption=none${config_JSON.jumppasscertificateverify?"&insecure=1&allowInsecure=1":""}#${encodeURIComponent(config_JSON.preferredsubscribegenerate.SUBNAME)}`,config_JSON.preferredsubscribegenerate.TOKEN=await MD5MD5(t+n);const d={BotToken:null,ChatID:null};config_JSON.TG={startuse:!!config_JSON.TG.startuse&&config_JSON.TG.startuse,...d};try{const t=await e.KV.get("tg.json");if(t){const e=JSON.parse(t);config_JSON.TG.ChatID=e.ChatID?e.ChatID:null,config_JSON.TG.BotToken=e.BotToken?maskcodesensitiveinfo(e.BotToken):null}else await e.KV.put("tg.json",JSON.stringify(d,null,2))}catch(e){console.error(`readtg.jsonouterror: ${e.message}`)}const u={Email:null,GlobalAPIKey:null,AccountID:null,APIToken:null,UsageAPI:null};config_JSON.CF={...u,Usage:{success:!1,pages:0,workers:0,total:0,max:1e5}};try{const t=await e.KV.get("cf.json");if(t){const e=JSON.parse(t);if(e.UsageAPI)try{const t=await fetch(e.UsageAPI),n=await t.json();config_JSON.CF.Usage=n}catch(e){console.error(`pleaserequest CF_JSON.UsageAPI failed: ${e.message}`)}else{config_JSON.CF.Email=e.Email?e.Email:null,config_JSON.CF.GlobalAPIKey=e.GlobalAPIKey?maskcodesensitiveinfo(e.GlobalAPIKey):null,config_JSON.CF.AccountID=e.AccountID?maskcodesensitiveinfo(e.AccountID):null,config_JSON.CF.APIToken=e.APIToken?maskcodesensitiveinfo(e.APIToken):null,config_JSON.CF.UsageAPI=null;const t=await getCloudflareUsage(e.Email,e.GlobalAPIKey,e.AccountID,e.APIToken);config_JSON.CF.Usage=t}}else await e.KV.put("cf.json",JSON.stringify(u,null,2))}catch(e){console.error(`readcf.jsonouterror: ${e.message}`)}return config_JSON.plusloadtime=(performance.now()-a).toFixed(2)+"ms",config_JSON}async function generaterandomIP(e,t=16,n=-1){const s={9808:"cmcc",4837:"cu",4134:"ct"},r=e.cf.asn,o=s[r]?`https://raw.githubusercontent.com/cmliu/cmliu/main/CF-CIDR/${s[r]}.txt`:"https://raw.githubusercontent.com/cmliu/cmliu/main/CF-CIDR.txt",a={9808:"CFmovemovepreferred",4837:"CFconnectthroughpreferred",4134:"CFtelecompreferred"}[r]||"CFofficialmethodpreferred",i=[443,2053,2083,2087,2096,8443];let c=[];try{const e=await fetch(o);c=e.ok?await parseToArray(await e.text()):["104.16.0.0/13"]}catch{c=["104.16.0.0/13"]}const l=Array.from({length:t},()=>`${(e=>{const[t,n]=e.split("/"),s=32-parseInt(n),r=((t.split(".").reduce((e,t,n)=>e|parseInt(t)<<24-8*n,0)&4294967295<>>0)>>>0)+Math.floor(Math.random()*Math.pow(2,s))>>>0;return[r>>>24&255,r>>>16&255,r>>>8&255,255&r].join(".")})(c[Math.floor(Math.random()*c.length)])}:${-1===n?i[Math.floor(Math.random()*i.length)]:n}#${a}`);return[l,l.join("\n")]}async function parseToArray(e){var t=e.replace(/[ "'\r\n]+/g,",").replace(/,+/g,",");","==t.charAt(0)&&(t=t.slice(1)),","==t.charAt(t.length-1)&&(t=t.slice(0,t.length-1));return t.split(",")}function isValidBase64(e){if("string"!=typeof e)return!1;const t=e.replace(/\s/g,"");if(0===t.length||t.length%4!=0)return!1;if(!/^[A-Za-z0-9+/]+={0,2}$/.test(t))return!1;try{return atob(t),!0}catch{return!1}}function base64Decode(e){const t=new Uint8Array(atob(e).split("").map(e=>e.charCodeAt(0)));return new TextDecoder("utf-8").decode(t)}async function pleaserequestpreferredAPI(e,t="443",n=3e3){if(!e?.length)return[[],[],[]];const s=new Set;let r="";await Promise.allSettled(e.map(async e=>{try{const o=new AbortController,a=setTimeout(()=>o.abort(),n),i=await fetch(e,{signal:o.signal});clearTimeout(a);let c="";try{const e=await i.arrayBuffer(),t=(i.headers.get("content-type")||"").toLowerCase(),n=t.match(/charset=([^\s;]+)/i)?.[1]?.toLowerCase()||"";let s=["utf-8","gb2312"];(n.includes("gb")||n.includes("gbk")||n.includes("gb2312"))&&(s=["gb2312","utf-8"]);let r=!1;for(const t of s)try{const n=new TextDecoder(t).decode(e);if(n&&n.length>0&&!n.includes("�")){c=n,r=!0;break}if(n&&n.length>0)continue}catch(e){continue}if(r||(c=await i.text()),!c||0===c.trim().length)return}catch(e){return void console.error("Failed to decode response:",e)}const l=isValidBase64(c)?base64Decode(c):c;if(l.split("#")[0].includes("://"))return void(r+=l+"\n");const d=c.trim().split("\n").map(e=>e.trim()).filter(e=>e),u=d.length>1&&d[0].includes(","),p=/^[^\[\]]*:[^\[\]]*:[^\[\]]/;if(u){const n=d[0].split(",").map(e=>e.trim()),r=d.slice(1);if(n.includes("IPplaceaddress")&&n.includes("port")&&n.includes("dataincenter")){const e=n.indexOf("IPplaceaddress"),t=n.indexOf("port"),o=n.indexOf("country")>-1?n.indexOf("country"):n.indexOf("city")>-1?n.indexOf("city"):n.indexOf("dataincenter"),a=n.indexOf("TLS");r.forEach(n=>{const r=n.split(",").map(e=>e.trim());if(-1!==a&&"true"!==r[a]?.toLowerCase())return;const i=p.test(r[e])?`[${r[e]}]`:r[e];s.add(`${i}:${r[t]}#${r[o]}`)})}else if(n.some(e=>e.includes("IP"))&&n.some(e=>e.includes("delay"))&&n.some(e=>e.includes("downloadspeed"))){const o=n.findIndex(e=>e.includes("IP")),a=n.findIndex(e=>e.includes("delay")),i=n.findIndex(e=>e.includes("downloadspeed")),c=new URL(e).searchParams.get("port")||t;r.forEach(e=>{const t=e.split(",").map(e=>e.trim()),n=p.test(t[o])?`[${t[o]}]`:t[o];s.add(`${n}:${c}#CFpreferred ${t[a]}ms ${t[i]}MB/s`)})}}else d.forEach(n=>{const r=n.indexOf("#"),[o,a]=r>-1?[n.substring(0,r),n.substring(r)]:[n,""];let i=!1;if(o.startsWith("["))i=/\]:(\d+)$/.test(o);else{const e=o.lastIndexOf(":");i=e>-1&&/^\d+$/.test(o.substring(e+1))}const c=new URL(e).searchParams.get("port")||t;s.add(i?n:`${o}:${c}${a}`)})}catch(e){}}));const o=r.trim()?[...new Set(r.split(/\r?\n/).filter(e=>""!==e.trim()))]:[];return[Array.from(s),o,[]]}async function reversedelegateparamsget(e){const t=new URL(e.url),{pathname:n,searchParams:s}=t,r=n.toLowerCase();mySocks5Account=s.get("socks5")||s.get("http")||null,enableGlobalSocks5=s.has("globalproxy")||!1;const o=r.match(/\/(proxyip[.=]|pyip=|ip=)(.+)/);if(s.has("proxyip")){const e=s.get("proxyip");return proxyIP=e.includes(",")?e.split(",")[Math.floor(Math.random()*e.split(",").length)]:e,void(enableProxyFallback=!1)}if(o){const e="proxyip."===o[1]?`proxyip.${o[2]}`:o[2];return proxyIP=e.includes(",")?e.split(",")[Math.floor(Math.random()*e.split(",").length)]:e,void(enableProxyFallback=!1)}let a;if(a=n.match(/\/(socks5?|http):\/?\/?(.+)/i)){if(enableSocks5Proxy="http"===a[1].toLowerCase()?"http":"socks5",mySocks5Account=a[2].split("#")[0],enableGlobalSocks5=!0,mySocks5Account.includes("@")){const e=mySocks5Account.lastIndexOf("@");let t=mySocks5Account.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)),mySocks5Account=`${t}@${mySocks5Account.substring(e+1)}`}}else if(a=n.match(/\/(g?s5|socks5|g?http)=(.+)/i)){const e=a[1].toLowerCase();mySocks5Account=a[2],enableSocks5Proxy=e.includes("http")?"http":"socks5",enableGlobalSocks5=e.startsWith("g")||enableGlobalSocks5}if(mySocks5Account)try{parsedSocks5Address=await getSOCKS5accountid(mySocks5Account),enableSocks5Proxy=s.get("http")?"http":enableSocks5Proxy}catch(e){console.error("parseSOCKS5placeaddressfailed:",e.message),enableSocks5Proxy=null}else enableSocks5Proxy=null}async function getSOCKS5accountid(e){if(e.includes("@")){const t=e.lastIndexOf("@");let n=e.substring(0,t).replaceAll("%3D","=");/^(?:[A-Z0-9+/]{4})*(?:[A-Z0-9+/]{2}==|[A-Z0-9+/]{3}=)?$/i.test(n)&&!n.includes(":")&&(n=atob(n)),e=`${n}@${e.substring(t+1)}`}const t=e.lastIndexOf("@"),[n,s]=-1===t?[e,void 0]:[e.substring(t+1),e.substring(0,t)];let r,o,a,i;if(s&&([r,o]=s.split(":"),!o))throw new Error('invalid SOCKS placeaddressformat:confirmcertpartsplitmustis "username:password" formstyle');if(n.includes("]:"))[a,i]=[n.split("]:")[0]+"]",Number(n.split("]:")[1].replace(/[^\d]/g,""))];else if(n.startsWith("["))[a,i]=[n,80];else{const e=n.split(":");[a,i]=2===e.length?[e[0],Number(e[1].replace(/[^\d]/g,""))]:[n,80]}if(isNaN(i))throw new Error("invalid SOCKS placeaddressformat:portidmustisnumberchar");if(a.includes(":")&&!/^\[.*\]$/.test(a))throw new Error("invalid SOCKS placeaddressformat:IPv6 placeaddressmustusemethodbracketidbracketup,if [2001:db8::1]");return{username:r,password:o,hostname:a,port:i}}async function getCloudflareUsage(e,t,n,s){const r="https://api.cloudflare.com/client/v4",o=e=>e?.reduce((e,t)=>e+(t?.sum?.requests||0),0)||0,a={"Content-Type":"application/json"};try{if(!(n||e&&t))return{success:!1,pages:0,workers:0,total:0,max:1e5};if(!n){const s=await fetch(`${r}/accounts`,{method:"GET",headers:{...a,"X-AUTH-EMAIL":e,"X-AUTH-KEY":t}});if(!s.ok)throw new Error(`accountusergetfailed: ${s.status}`);const o=await s.json();if(!o?.result?.length)throw new Error("notfindtoaccountuser");const i=o.result.findIndex(t=>t.name?.toLowerCase().startsWith(e.toLowerCase()));n=o.result[i>=0?i:0]?.id}const i=new Date;i.setUTCHours(0,0,0,0);const c=s?{...a,Authorization:`Bearer ${s}`}:{...a,"X-AUTH-EMAIL":e,"X-AUTH-KEY":t},l=await fetch(`${r}/graphql`,{method:"POST",headers:c,body:JSON.stringify({query:"query getBillingMetrics($AccountID: String!, $filter: AccountWorkersInvocationsAdaptiveFilter_InputObject) {\n viewer { accounts(filter: {accountTag: $AccountID}) {\n pagesFunctionsInvocationsAdaptiveGroups(limit: 1000, filter: $filter) { sum { requests } }\n workersInvocationsAdaptive(limit: 10000, filter: $filter) { sum { requests } }\n } }\n }",variables:{AccountID:n,filter:{datetime_geq:i.toISOString(),datetime_leq:(new Date).toISOString()}}})});if(!l.ok)throw new Error(`queryfailed: ${l.status}`);const d=await l.json();if(d.errors?.length)throw new Error(d.errors[0].message);const u=d?.data?.viewer?.accounts?.[0];if(!u)throw new Error("notfindtoaccountuserdata");const p=o(u.pagesFunctionsInvocationsAdaptiveGroups),f=o(u.workersInvocationsAdaptive),h=p+f,g=1e5;return console.log(`statsresult - Pages: ${p}, Workers: ${f}, totalcount: ${h}, uplimit: 100000`),{success:!0,pages:p,workers:f,total:h,max:g}}catch(e){return console.error("getmakeuseamounterror:",e.message),{success:!1,pages:0,workers:0,total:0,max:1e5}}}function sha224(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;for(e+=String.fromCharCode(128);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,f]=r;for(let e=0;e<64;e++){const r=f+(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;f=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:f)>>>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}async function parseplaceaddressport(e,t="dash.cloudflare.com",n="00000000-0000-4000-8000-000000000000"){if(cacheproxyIP&&cachedProxyArray&&cacheproxyIP===e)console.log(`[reversedelegateparse] readcache totalnumber: ${cachedProxyArray.length}one\n${cachedProxyArray.map(([e,t],n)=>`${n+1}. ${e}:${t}`).join("\n")}`);else{async function s(e,t){try{const n=await fetch(`https://1.1.1.1/dns-query?name=${e}&type=${t}`,{headers:{Accept:"application/dns-json"}});if(!n.ok)return[];return(await n.json()).Answer||[]}catch(e){return console.error(`DoHqueryfailed (${t}):`,e),[]}}function r(e){let t=e,n=443;if(e.includes("]:")){const s=e.split("]:");t=s[0]+"]",n=parseInt(s[1],10)||n}else if(e.includes(":")&&!e.startsWith("[")){const s=e.lastIndexOf(":");t=e.slice(0,s),n=parseInt(e.slice(s+1),10)||n}return[t,n]}let o=[];if((e=e.toLowerCase()).includes(".william"))try{const d=(await s(e,"TXT")).filter(e=>16===e.type).map(e=>e.data);if(d.length>0){let u=d[0];u.startsWith('"')&&u.endsWith('"')&&(u=u.slice(1,-1));o=u.replace(/\\010/g,",").replace(/\n/g,",").split(",").map(e=>e.trim()).filter(Boolean).map(e=>r(e))}}catch(p){console.error("parseWilliamdomainfailed:",p)}else{let[f,h]=r(e);if(e.includes(".tp")){const m=e.match(/\.tp(\d+)/);m&&(h=parseInt(m[1],10))}const g=/^\[?([a-fA-F0-9:]+)\]?$/;if(/^(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?)$/.test(f)||g.test(f))o=[[f,h]];else{const[y,w]=await Promise.all([s(f,"A"),s(f,"AAAA")]),b=[...y.filter(e=>1===e.type).map(e=>e.data),...w.filter(e=>28===e.type).map(e=>`[${e.data}]`)];o=b.length>0?b.map(e=>[e,h]):[[f,h]]}}const a=o.sort((e,t)=>e[0].localeCompare(t[0])),i=t.includes(".")?t.split(".").slice(-2).join("."):t;let c=[...i+n].reduce((e,t)=>e+t.charCodeAt(0),0);console.log(`[reversedelegateparse] randomseed: ${c}\ntargetstationpoint: ${i}`);const l=[...a].sort(()=>(c=1103515245*c+12345&2147483647)/2147483647-.5);cachedProxyArray=l.slice(0,8),console.log(`[reversedelegateparse] parsecomplete totalnumber: ${cachedProxyArray.length}one\n${cachedProxyArray.map(([e,t],n)=>`${n+1}. ${e}:${t}`).join("\n")}`),cacheproxyIP=e}return cachedProxyArray}async function SOCKS5canuseabilityverify(e="socks5",t){const n=Date.now();try{parsedSocks5Address=await getSOCKS5accountid(t)}catch(s){return{success:!1,error:s.message,proxy:e+"://"+t,responseTime:Date.now()-n}}const{username:s,password:r,hostname:o,port:a}=parsedSocks5Address,i=s&&r?`${s}:${r}@${o}:${a}`:`${o}:${a}`;try{const t=new Uint8Array(0),s="socks5"==e?await socks5Connect("check.socks5.090227.xyz",80,t):await httpConnect("check.socks5.090227.xyz",80,t);if(!s)return{success:!1,error:"nonemethodconnectiontoproxyservicehandler",proxy:e+"://"+i,responseTime:Date.now()-n};try{const t=s.writable.getWriter(),r=new TextEncoder;await t.write(r.encode("GET /cdn-cgi/trace HTTP/1.1\r\nHost: check.socks5.090227.xyz\r\nConnection: close\r\n\r\n")),t.releaseLock();const o=s.readable.getReader(),a=new TextDecoder;let c="";try{for(;;){const{done:e,value:t}=await o.read();if(e)break;c+=a.decode(t,{stream:!0})}}finally{o.releaseLock()}return await s.close(),{success:!0,proxy:e+"://"+i,ip:c.match(/ip=(.*)/)[1],loc:c.match(/loc=(.*)/)[1],responseTime:Date.now()-n}}catch(t){try{await s.close()}catch(e){console.log("closeconnectionwhenouterror:",e)}return{success:!1,error:t.message,proxy:e+"://"+i,responseTime:Date.now()-n}}}catch(t){return{success:!1,error:t.message,proxy:e+"://"+i,responseTime:Date.now()-n}}}async function nginx(){return'\n\t\n\t\n\t\n\tWelcome to nginx! \n\t\n\n\n\tWelcome to nginx!
\n\tIf you see this page, the nginx web server is successfully installed and\n\tworking. Further configuration is required.
\n\t\n\tFor online documentation and support please refer to\n\tnginx.org.
\n\tCommercial support is available at\n\tnginx.com.
\n\t\n\tThank you for using nginx.
\n\t\n\t\n\t'}async function html1101(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