```
├── .github/
├── workflows/
├── obfuscate.yml (700 tokens)
├── test.yml (400 tokens)
├── README.md (1800 tokens)
├── snippets (6.3k tokens)
├── wrangler.toml
├── ÃÂçñóÃÂ.md (2.4k tokens)
├── å°Âå¹´ä½ ç¸信åÂ
Âå (329.2k tokens)
├── æÂÂæÂÂæºÂå (68.7k tokens)
```
## /.github/workflows/obfuscate.yml
```yml path="/.github/workflows/obfuscate.yml"
name: Generate and Obfuscate Worker Script
on:
workflow_dispatch:
push:
branches:
- '**' # 仅匹配分支推送,排除标签推送
paths:
- '明文源吗'
jobs:
build-and-obfuscate:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Obfuscator
run: npm install javascript-obfuscator
- name: Obfuscate from local source file
run: |
cat > obfuscate.js << 'EOF'
const JavaScriptObfuscator = require('javascript-obfuscator');
const fs = require('fs');
const path = require('path');
const sourceFileName = '明文源吗';
const outputFileName = '少年你相信光吗';
const sourceFilePath = path.join(process.cwd(), sourceFileName);
if (!fs.existsSync(sourceFilePath)) {
console.error('错误:在路径 \'' + sourceFilePath + '\' 未找到源文件。请确保您的仓库根目录有名为 \'明文源吗\' 的文件。');
process.exit(1);
}
const originalCode = fs.readFileSync(sourceFilePath, 'utf8');
if (!originalCode || originalCode.trim().length === 0) {
console.error('错误:源文件 ' + sourceFileName + ' 为空。');
process.exit(1);
}
const obfuscationOptions = {
compact: true,
controlFlowFlattening: false,
controlFlowFlatteningThreshold: 0,
deadCodeInjection: false,
stringArray: true,
stringArrayEncoding: ['base64'],
stringArrayThreshold: 1.0,
stringArrayRotate: true,
stringArrayShuffle: true,
stringArrayWrappersCount: 2,
stringArrayWrappersChainedCalls: false,
stringArrayWrappersParametersMaxCount: 3,
renameGlobals: true,
identifierNamesGenerator: 'mangled-shuffled',
identifierNamesCache: null,
identifiersPrefix: '',
renameProperties: false,
renamePropertiesMode: 'safe',
ignoreImports: false,
target: 'browser',
numbersToExpressions: false,
simplify: false,
splitStrings: true,
splitStringsChunkLength: 1,
transformObjectKeys: false,
unicodeEscapeSequence: true,
selfDefending: false,
debugProtection: false,
debugProtectionInterval: 0,
disableConsoleOutput: true,
domainLock: []
};
const obfuscatedCode = JavaScriptObfuscator.obfuscate(originalCode, obfuscationOptions).getObfuscatedCode();
fs.writeFileSync(path.join(process.cwd(), outputFileName), obfuscatedCode, 'utf8');
console.log('成功将 \'' + sourceFileName + '\' 混淆并保存至 \'' + outputFileName + '\'。');
EOF
node obfuscate.js
- name: Commit and push the obfuscated file
run: |
git config --global user.name 'GitHub Actions Bot'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add '少年你相信光吗'
if git diff --staged --quiet; then
echo "No changes to commit, the obfuscated file is already up-to-date."
else
git commit -m "部署用这个"
git push
fi
```
## /.github/workflows/test.yml
```yml path="/.github/workflows/test.yml"
name: Deploy Worker Script
on:
push:
tags:
- '*' # 当任何标签被推送时触发
jobs:
deploy-worker:
runs-on: ubuntu-latest
steps:
- name: Get tag name
id: get_tag
run: |
TAG_NAME=${GITHUB_REF#refs/tags/}
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
echo "当前标签: $TAG_NAME"
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # 需要完整的Git历史来获取标签信息
- name: Check if source file exists
run: |
if [ ! -f "少年你相信光吗" ]; then
echo "错误:在项目根目录未找到 '少年你相信光吗' 文件。"
exit 1
fi
echo "成功找到源文件 '少年你相信光吗'"
- name: Rename file to _worker.js
run: |
cp "少年你相信光吗" "_worker.js"
echo "成功将 '少年你相信光吗' 复制为 '_worker.js'"
echo "注意:文件不会提交到仓库,仅用于 Release"
- name: Create zip file
run: |
zip Pages.zip _worker.js wrangler.toml
echo "成功将 '_worker.js' 和 'wrangler.toml' 压缩为 'Pages.zip'"
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.get_tag.outputs.tag_name }}
name: Pages ${{ steps.get_tag.outputs.tag_name }}
body: |
代码重构支持明文部署
draft: false
prerelease: false
files: Pages.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Output summary
run: |
echo "## 部署完成" >> $GITHUB_STEP_SUMMARY
echo "- 源文件: 少年你相信光吗" >> $GITHUB_STEP_SUMMARY
echo "- 目标文件: _worker.js" >> $GITHUB_STEP_SUMMARY
echo "- 压缩文件: Pages.zip" >> $GITHUB_STEP_SUMMARY
echo "- 标签: ${{ steps.get_tag.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
echo "- GitHub Release 已创建" >> $GITHUB_STEP_SUMMARY
```
## /README.md
# CFnew - 终端 v2.9.8b
> **⚠️ 重要:部署后请将兼容日期设置为 `2026-01-20`**
>
> **Pages 部署:**
> 1. 登录 [Cloudflare 控制台](https://dash.cloudflare.com/)
> 2. 进入 **Workers 和 Pages** → 选择你的 Pages 项目
> 3. 点击 **设置** → **运行时**
> 4. 找到 **兼容性日期**,选择 `2026-01-20`,点击 **保存**
> 5. 返回 **部署** → **创建部署** → 上传文件
>
> **Worker 部署:**
> 1. 登录 [Cloudflare 控制台](https://dash.cloudflare.com/)
> 2. 进入 **Workers 和 Pages** → 选择你的 Worker
> 3. 点击 **设置** → **运行时**
> 4. 找到 **兼容性日期**,选择 `2026-01-20`,点击 **保存**
**语言:** [中文](README.md) | [فارسی](فارسی.md)
[Telegram 交流群](https://t.me/+ft-zI76oovgwNmRh)
## 主要功能
- 多协议支持:VLESS、Trojan、xhttp,可以同时启用多个
- 自定义路径:不用UUID当路径了,可以自己设置,支持多级路径
- 延迟测试:内置测试工具,测IP延迟,自动获取机场码
- 订阅转换:可以自定义转换服务地址
- 图形化管理:用KV存配置,改完立即生效,不用重新部署
- API管理:支持通过API动态添加/删除优选IP
- 多客户端:支持 CLASH、SURGE、SING-BOX、LOON、QUANTUMULT X、V2RAY、Shadowrocket、STASH、NEKORAY、V2RAYNG
- 应用唤醒:点按钮自动打开对应客户端
- 自动识别:根据User-Agent自动返回对应格式
- 多语言:支持中文和波斯语,根据浏览器语言自动切换
## v2.9.8b 更新
- 订阅转换内部实现:Clash / Stash / Sing-box / Surge / Loon / Quantumult X 配置全部由 Worker 直接生成,不再依赖任何外部 sub-converter
- 完整规则集:Clash 使用 Loyalsoldier `rule-providers`;Sing-box 使用 MetaCubeX SRS;Surge / Loon / QuanX 使用 ACL4SSR / blackmatrix7 远端规则
- 各策略分组均包含「策略组 + 全部节点」,可直接切换具体节点(已移除「自动选择」url-test,避免周期性测速浪费请求)
- 修复 Clash IPv6 节点 `server` 被解析为数组、代理组 `🎯 全球直连` ↔ `🚀 节点选择` 循环引用等问题
- 传输优化:参考 GrainTCP 思路优化 WebSocket/TCP 转发,上行小包队列合并、下行小包聚合、大包直发,并优化 VLESS 解析热路径
- 图形化 ALPN:新增 `alpn` 下拉选项,留空时不写 `alpn`,也可选择 `h3`、`h2`、`http/1.1` 或组合值
- 节点别名简化:域名统一为 `优选域名-序号`,IPv6 统一为 `IPv6优选-序号`,IPv4 使用 `isp-colo-序号`
- KV 配置缓存:30s 短窗口 + 跨 isolate 版本键 `c_ver`,保存后无需刷新两次
- SOCKS5 降级超时:直连 3.5s 无数据自动走 fallback
- 标签:「启用 GitHub 默认优选」改为「启用自定义优选」
- 页面特效开关:`FX: ON / OFF`,选择 localStorage 持久化
- 提供混淆版本 `少年你相信光吗`,逻辑与 `明文源吗` 完全一致
## v2.9.7 更新
- 悬浮保存按钮:右下角常驻「保存全部」按钮,支持 `Ctrl+S` / `Cmd+S` 快捷键
- 编辑任意字段后按钮自动进入「未保存」提示状态
- 保存中 / 刷新中有进度反馈
- 通知体验优化:所有阻塞式弹窗替换为右上角浮动消息,自动消失、可悬停暂停、支持手动关闭
- 4 种语义:success / info / warn / error
- 操作按钮整合:将分散在各区块的 4 个保存按钮合并为统一的悬浮操作组
- 提供混淆版本 `少年你相信光吗`,逻辑与 `明文源吗` 完全一致
## v2.9.6 更新
- 兼容 Xray-core v26.3.27
- 新增香港 (HK) 地区 ProxyIP 和地区选择
- KV 读取性能优化:5 小时内存缓存,减少 99% 以上的 KV 读取量
- 无效请求拦截:非法路径直接返回 404,不再触发 KV 读取
- 修复优选列表保存时 SOCKS5 配置 key 错误的问题
## v2.9.5 更新
- GitHub 默认优选地址默认关闭,需自行配置优选IP来源URL
- 新增「启用原生地址」开关,可在管理面板中控制是否生成原生地址节点(默认关闭)
- 兼容日期设置为 `2026-01-20`
## v2.9.4 更新
- 支持客户端通过 WebSocket path 参数覆盖连接级变量(`p`、`wk`、`rm`、`s`)
- 无需为每个节点单独部署 Worker,在分享链接的 path 里直接写参数即可
- 优先级:path 参数 > KV/环境变量全局配置 > 自动检测
- 详见下方「[客户端 path 参数](#客户端-path-参数)」说明
## v2.9.3 更新
- 新增图形化自定义DNS和ECH域名功能
- 可在界面中自定义DNS服务器地址(DoH格式)
- 可在界面中自定义ECH域名
- 支持动态更改,保存后立即生效
- Clash配置中的ech-opts增加query-server-name参数,与v2ray保持一致
## v2.9.2 更新
- 修复 Clash 配置生成问题
## v2.9.1 更新
- ECH支持:新增 Encrypted Client Hello (ECH) 功能
- 每次刷新订阅时自动获取最新的 ECH 配置
- 启用 ECH 时自动启用"仅 TLS"模式,避免 80 端口干扰
- 图形界面可一键开启/关闭 ECH 功能
## v2.9 更新
- 地区筛选:可以按地区筛选优选结果,支持多选
- 延迟筛选:新增"只显示最快的10个"选项
- 追加/替换模式:添加优选结果时可以追加或替换整个列表
- 结果展示优化:显示地区标签,按延迟排序
- 其他细节优化
---
### 相关工具
- 优选工具:https://github.com/byJoey/yx-tools/releases
- 文字教程:https://joeyblog.net/yuanchuang/1146.html
- Workers视频教程:https://www.youtube.com/watch?v=aYzTr8FafN4
- Pages视频教程:https://www.youtube.com/watch?v=JhVxJChDL-E
- Snippets视频教程:https://www.youtube.com/watch?v=xeFeH3Akcu8
### 部署
订阅每15分钟自动优选一次
#### 基础配置
| 变量名 | 值 | 说明 |
| :--- | :--- | :--- |
| `u` | 你的 UUID | 必需,用于访问订阅和配置界面 |
| `p` | proxyip | 可选,自定义ProxyIP地址和端口,支持 IPv4/IPv6/域名。设置后 `wk` 地区匹配失效(互斥)。也可在节点 path 里单独指定 |
| `s` | 你的SOCKS5地址 | 可选,格式:`user:pass@host:port` 或 `host:port`。也可在节点 path 里单独指定 |
| `d` | 自定义路径 | 可选,如 `/mypath` 或 `/path/to/sub`,不填用UUID路径。路径没 `/` 开头会自动补上 |
| `wk` | 地区代码 | 可选,手动指定Worker地区,如 `SG`、`HK`、`US`、`JP`。设置 `p` 后此项失效(互斥)。也可在节点 path 里单独指定 |
#### 协议配置
| 变量名 | 值 | 说明 |
| :--- | :--- | :--- |
| `ev` | yes/no | 可选,启用VLESS(默认启用) |
| `et` | yes/no | 可选,启用Trojan(默认禁用) |
| `ex` | yes/no | 可选,启用xhttp(默认禁用) |
| `tp` | 自定义密码 | 可选,Trojan密码,留空用UUID |
| `ech` | yes/no | 可选,启用ECH功能(默认禁用) |
| `alpn` | ALPN列表 | 可选,TLS节点ALPN参数。留空不写,由客户端协商;可选 `h3`、`h2`、`http/1.1`、`h3,h2`、`h2,http/1.1`、`h3,h2,http/1.1` |
#### 图形化配置(推荐)
1. 在Workers中创建KV命名空间,绑定环境变量 `C`
2. 部署后访问 `/{你的UUID}` 使用图形化配置
3. 改完配置立即生效,不用重新部署
#### 高级控制
| 变量名 | 值 | 说明 |
| :--- | :--- | :--- |
| `yx` | 自定义优选IP/域名 | 可选,支持命名,格式:`1.1.1.1:443#香港节点,8.8.8.8:53#Google DNS` |
| `yxURL` | 优选IP来源URL | 可选,自定义IP列表来源,留空用默认 |
| `scu` | 订阅转换地址 | 可选,默认:`https://url.v1.mk/sub` |
| `epd` | yes/no | 可选,启用优选域名(默认启用) |
| `epi` | yes/no | 可选,启用优选IP(默认启用) |
| `egi` | yes/no | 可选,启用GitHub默认优选(默认启用) |
| `qj` | no | 可选,设为`no`启用降级:CF直连失败→SOCKS5→fallback |
| `dkby` | yes | 可选,设为`yes`只生成TLS节点 |
| `ech` | yes/no | 可选,启用ECH功能(默认禁用,启用后自动开启仅TLS模式) |
| `alpn` | ALPN列表 | 可选,只写入TLS节点链接参数,留空则不写 |
| `yxby` | yes | 可选,设为`yes`关闭所有优选功能 |
| `rm` | no | 可选,设为`no`关闭地区智能匹配 |
| `ae` | yes | 可选,设为`yes`允许API管理(默认关闭) |
#### KV存储设置(推荐)
1. 在Cloudflare Workers中创建KV命名空间
2. 在Workers设置中绑定KV,变量名设为 `C`
3. 重新部署
4. 访问 `/{你的UUID}` 使用图形化配置
#### API使用
1. 下载优选软件:https://github.com/byJoey/yx-tools/releases
2. 开启API:访问 `/{UUID}` 或 `/{自定义路径}`,找到"允许API管理",开启后保存
3. 添加单个IP:
```bash
# 使用UUID路径
curl -X POST "https://your-worker.workers.dev/{UUID}/api/preferred-ips" \
-H "Content-Type: application/json" \
-d '{"ip": "1.2.3.4", "port": 443, "name": "香港节点"}'
# 使用自定义路径(如果设置了d变量)
curl -X POST "https://your-worker.workers.dev/{自定义路径}/api/preferred-ips" \
-H "Content-Type: application/json" \
-d '{"ip": "1.2.3.4", "port": 443, "name": "香港节点"}'
```
4. 批量添加IP:
```bash
curl -X POST "https://your-worker.workers.dev/{UUID或自定义路径}/api/preferred-ips" \
-H "Content-Type: application/json" \
-d '[
{"ip": "1.2.3.4", "port": 443, "name": "节点1"},
{"ip": "5.6.7.8", "port": 8443, "name": "节点2"}
]'
```
5. 清空所有IP:
```bash
curl -X DELETE "https://your-worker.workers.dev/{UUID或自定义路径}/api/preferred-ips" \
-H "Content-Type: application/json" \
-d '{"all": true}'
```
### 功能说明
#### 延迟测试
v2.7开始提供,v2.9增强了筛选功能
- 内置测试工具,不用装其他软件,直接在配置页面测IP延迟
- IP来源:
- 手动输入:直接输IP或域名,支持批量(逗号分隔)
- CF随机IP:从Cloudflare IP段随机生成
- URL获取:从远程URL获取IP列表
- 支持1-50线程并发测试,默认5线程
- 自动获取机场码(如SJC、LAX)
- 自动映射中文机场名(SJC→圣何塞)
- 自动扣除DNS+TLS握手时间,显示真实延迟
- 设置自动保存到浏览器
- 支持按地区筛选
- 支持只显示最快的10个
- 支持追加或替换模式
#### 多协议支持
- VLESS:默认启用
- Trojan:支持Trojan-WS-TLS,可以自定义密码,不填就用UUID
- xhttp:基于HTTP POST的伪装协议
- 可以同时启用多个协议,客户端会自动识别
- 图形界面一键开关
- 协议配置有独立保存按钮
#### ECH 功能 (Encrypted Client Hello)
- 支持 Encrypted Client Hello (ECH) 加密客户端握手
- 自动获取:每次刷新订阅时自动从 DoH 获取最新的 ECH 配置
- 优先使用 Google DNS,失败时自动尝试 Cloudflare DNS
- 智能模式:启用 ECH 时自动启用"仅 TLS"模式,避免 80 端口干扰
- 图形界面:可在协议配置区域一键开启/关闭
- 调试信息:在浏览器开发者工具的响应头中可查看详细的 ECH 获取过程
- 响应头信息:
- `X-ECH-Status`: SUCCESS 或 FAILED
- `X-ECH-Debug`: 详细的调试信息
- `X-ECH-Config-Length`: ECH 配置长度(成功时)
#### 自定义路径(d变量)
- 不用UUID当路径了,可以自己设置
- 支持多级路径,如 `/path/to/sub`
- 路径没 `/` 开头会自动补上
- 自定义路径后UUID路径自动禁用
- 可以随时在图形界面改路径
#### 图形化配置
- 用Cloudflare KV存配置
- 访问 `/{你的UUID}` 或 `/{自定义路径}` 就能用
- 改完立即生效,不用重新部署
- 优先级:KV配置 > 环境变量 > 默认值
#### 多语言支持
- 根据浏览器语言自动选择中文或波斯语
- 右上角可以手动切换
- 语言选择会保存到浏览器
- 波斯语自动启用RTL布局
#### 订阅转换控制
- 可以自定义转换服务URL
- 可以单独控制优选域名、优选IP、GitHub优选
- 默认全部启用
- 改完立即生效
#### API管理
- 通过RESTful API管理优选IP,不用改代码
- 支持批量添加
- 支持清空所有IP
- 默认关闭,需要在图形界面开启
- API添加的IP和手动配置的yx变量会自动合并
- API端点:
- `GET /{UUID或路径}/api/preferred-ips` - 查询列表
- `POST /{UUID或路径}/api/preferred-ips` - 添加(单个/批量)
- `DELETE /{UUID或路径}/api/preferred-ips` - 删除(单个/全部)
#### 客户端 path 参数
v2.9.4 新增。在 VLESS/Trojan 分享链接的 `path` 字段里追加查询参数,即可为**单个节点**单独指定连接级配置,无需额外部署 Worker。
| 参数 | 作用 | 示例 |
| :--- | :--- | :--- |
| `p` | 覆盖 ProxyIP(支持带端口) | `p=1.1.1.1` 或 `p=1.2.3.4:8443` |
| `wk` | 覆盖 Worker 地区 | `wk=jp`、`wk=us`、`wk=sg` |
| `rm` | 关闭地区智能匹配 | `rm=no` |
| `s` | 覆盖 SOCKS5 代理 | `s=user:pass@host:1080` |
**优先级:path 参数 > KV/环境变量 > 自动检测**
> ⚠️ **`p` 和 `wk` 互斥**:设置 `p` 后会直接使用指定的 ProxyIP,`wk` 的地区匹配逻辑被完全跳过,两者同时写只有 `p` 生效。
path 示例:
```
# 指定 ProxyIP(不要同时写 wk)
/?ed=2048&p=1.1.1.1
/?ed=2048&p=proxy.example.com:443
/?ed=2048&p=[2001:db8::1]:443
# 指定地区(让 Worker 自动选该地区的 ProxyIP)
/?ed=2048&wk=jp
/?ed=2048&wk=sg&rm=no
# 指定 SOCKS5(可与 wk 搭配)
/?ed=2048&s=user:pass@socks5.host:1080&wk=us
```
> 不在上表中的变量(如 `ev`、`et`、`yx` 等)属于订阅生成级配置,在 WebSocket 握手阶段已过路由,放在 path 里无效,仍需在环境变量或 KV 中设置。
#### 手动指定地区
- 可以手动指定Worker地区,覆盖自动检测
- 设置方式:`wk=SG` 或图形界面选择,或在节点 path 里加 `wk=SG`
- 支持:US、SG、JP、HK、KR、DE、SE、NL、FI、GB
#### 优选节点命名
- 订阅别名默认使用短名称,不再追加端口、协议、TLS/WS 等信息
- 域名节点:`优选域名-01`、`优选域名-02`
- IPv6节点:`IPv6优选-01`、`IPv6优选-02`
- IPv4节点:优先使用 `isp-colo-序号`,缺少运营商信息时回退为 `IPv4优选-序号`
#### 系统状态
- 显示Worker地区、检测方式、ProxyIP状态
- 选择逻辑:同地区 → 邻近地区 → 其他地区
#### 高级控制
- `rm=no` 关闭地区智能匹配
- `qj=no` 启用降级模式(CF直连失败→SOCKS5→fallback)
- `dkby=yes` 只生成TLS节点
- `ech=yes` 启用ECH功能(启用后自动开启仅TLS模式)
- `alpn=h3,h2` 指定TLS节点ALPN,留空则不写
- `yxby=yes` 关闭所有优选功能
#### 多客户端支持
支持10种客户端:CLASH、SURGE、SING-BOX、LOON、QUANTUMULT X、V2RAY、Shadowrocket、STASH、NEKORAY、V2RAYNG
- 根据客户端类型自动生成配置
- 图形界面一键生成订阅链接
- 点按钮自动打开对应客户端
- 根据User-Agent自动识别并返回对应格式
- 不同客户端自动适配最佳协议组合
- TLS 链接默认不写 `alpn`,可在图形界面或通过 `alpn` 配置指定
#### 性能优化
- 每15分钟自动优选一次
- 多重备用方案
- 智能缓存,减少重复计算
### 致谢
- 基于 [zizifn/edgetunnel](https://github.com/zizifn/edgetunnel) 修改
- ProxyIP部分来自 [cmliu](https://github.com/cmliu)
- 反代IP来自 [qwer-search](https://github.com/qwer-search)
- 在线优选接口来自 [白嫖哥](https://t.me/bestcfipas)
## Star History
[](https://www.star-history.com/#byJoey/cfnew&Timeline&LogScale)
## /snippets
``` path="/snippets"
import { connect } from 'cloudflare:sockets';
// --- 硬编码配置 ---
const authToken = 'f64bdc57-0f54-4705-bf75-cfd646d98c06';
let fallbackAddress = '';
const socks5Config = '';
// 手动指定地区(留空则自动检测,可选值:US、SG、JP、HK、KR、DE、SE、NL、FI、GB)
const manualWorkerRegion = '';
// D短地址(自定义路径,留空则使用UUID路径,支持多级路径如:mypath 或 path/to/sub)
const customPath = '';
// GitHub订阅URL(硬编码)
const githubPreferredURL = 'https://raw.githubusercontent.com/qwer-search/bestip/refs/heads/main/kejilandbestip.txt';
// 启用GitHub优选IP(true启用,false禁用)
const enableGitHubPreferred = true;
// 启用其他优选(域名优选,true启用,false禁用)
const enableOtherPreferred = true;
// API地址配置(订阅转换服务)
const apiBaseUrl = 'https://url.v1.mk/sub';
const directDomains = [
{ name: "cloudflare.182682.xyz", domain: "cloudflare.182682.xyz" },
{ name: "speed.marisalnc.com", domain: "speed.marisalnc.com" },
{ domain: "freeyx.cloudflare88.eu.org" }, { domain: "bestcf.top" },
{ domain: "cdn.2020111.xyz" }, { domain: "cfip.cfcdn.vip" },
{ domain: "cf.0sm.com" }, { domain: "cf.090227.xyz" },
{ domain: "cf.zhetengsha.eu.org" }, { domain: "cloudflare.9jy.cc" },
{ domain: "cf.zerone-cdn.pp.ua" }, { domain: "cfip.1323123.xyz" },
{ domain: "cnamefuckxxs.yuchen.icu" }, { domain: "cloudflare-ip.mofashi.ltd" },
{ domain: "115155.xyz" }, { domain: "cname.xirancdn.us" },
{ domain: "f3058171cad.002404.xyz" }, { domain: "8.889288.xyz" },
{ domain: "cdn.tzpro.xyz" }, { domain: "cf.877771.xyz" },
{ domain: "xn--b6gac.eu.org" }
];
const parsedSocks5Config = {};
const isSocksEnabled = false;
let enableRegionMatching = true;
let currentWorkerRegion = '';
const backupIPs = [
{ domain: 'ProxyIP.US.CMLiussss.net', region: 'US', regionCode: 'US', port: 443 },
{ domain: 'ProxyIP.SG.CMLiussss.net', region: 'SG', regionCode: 'SG', port: 443 },
{ domain: 'ProxyIP.JP.CMLiussss.net', region: 'JP', regionCode: 'JP', port: 443 },
{ domain: 'ProxyIP.HK.CMLiussss.net', region: 'HK', regionCode: 'HK', port: 443 },
{ domain: 'ProxyIP.KR.CMLiussss.net', region: 'KR', regionCode: 'KR', port: 443 },
{ domain: 'ProxyIP.DE.CMLiussss.net', region: 'DE', regionCode: 'DE', port: 443 },
{ domain: 'ProxyIP.SE.CMLiussss.net', region: 'SE', regionCode: 'SE', port: 443 },
{ domain: 'ProxyIP.NL.CMLiussss.net', region: 'NL', regionCode: 'NL', port: 443 },
{ domain: 'ProxyIP.FI.CMLiussss.net', region: 'FI', regionCode: 'FI', port: 443 },
{ domain: 'ProxyIP.GB.CMLiussss.net', region: 'GB', regionCode: 'GB', port: 443 }
];
const E_INVALID_DATA = atob('aW52YWxpZCBkYXRh');
const E_INVALID_USER = atob('aW52YWxpZCB1c2Vy');
const E_UNSUPPORTED_CMD = atob('Y29tbWFuZCBpcyBub3Qgc3VwcG9ydGVk');
const E_UDP_DNS_ONLY = atob('VURQIHByb3h5IG9ubHkgZW5hYmxlIGZvciBETlMgd2hpY2ggaXMgcG9ydCA1Mw==');
const E_INVALID_ADDR_TYPE = atob('aW52YWxpZCBhZGRyZXNzVHlwZQ==');
const E_EMPTY_ADDR = atob('YWRkcmVzc1ZhbHVlIGlzIGVtcHR5');
const E_WS_NOT_OPEN = atob('d2ViU29ja2V0LmVhZHlTdGF0ZSBpcyBub3Qgb3Blbg==');
const E_INVALID_ID_STR = atob('U3RyaW5naWZpZWQgaWRlbnRpZmllciBpcyBpbnZhbGlk');
const E_INVALID_SOCKS_ADDR = atob('SW52YWxpZCBTT0NLUyBhZGRyZXNzIGZvcm1hdA==');
const E_SOCKS_NO_METHOD = atob('bm8gYWNjZXB0YWJsZSBtZXRob2Rz');
const E_SOCKS_AUTH_NEEDED = atob('c29ja3Mgc2VydmVyIG5lZWRzIGF1dGg=');
const E_SOCKS_AUTH_FAIL = atob('ZmFpbCB0byBhdXRoIHNvY2tzIHNlcnZlcg==');
const E_SOCKS_CONN_FAIL = atob('ZmFpbCB0byBvcGVuIHNvY2tzIGNvbm5lY3Rpb24=');
const ADDRESS_TYPE_IPV4 = 1;
const ADDRESS_TYPE_URL = 2;
const ADDRESS_TYPE_IPV6 = 3;
async function detectWorkerRegion(request) {
try {
const cfCountry = request.cf?.country;
if (cfCountry) {
const countryToRegion = {
'US': 'US', 'SG': 'SG', 'JP': 'JP', 'HK': 'HK', 'KR': 'KR',
'DE': 'DE', 'SE': 'SE', 'NL': 'NL', 'FI': 'FI', 'GB': 'GB',
'CN': 'HK', 'TW': 'HK', 'AU': 'SG', 'CA': 'US',
'FR': 'DE', 'IT': 'DE', 'ES': 'DE', 'CH': 'DE',
'AT': 'DE', 'BE': 'NL', 'DK': 'SE', 'NO': 'SE', 'IE': 'GB'
};
if (countryToRegion[cfCountry]) return countryToRegion[cfCountry];
}
return 'HK';
} catch (error) {
return 'HK';
}
}
function getNearbyRegions(region) {
const nearbyMap = {
'US': ['SG', 'JP', 'HK', 'KR'],
'SG': ['JP', 'HK', 'KR', 'US'],
'JP': ['SG', 'HK', 'KR', 'US'],
'HK': ['SG', 'JP', 'KR', 'US'],
'KR': ['JP', 'HK', 'SG', 'US'],
'DE': ['NL', 'GB', 'SE', 'FI'],
'SE': ['DE', 'NL', 'FI', 'GB'],
'NL': ['DE', 'GB', 'SE', 'FI'],
'FI': ['SE', 'DE', 'NL', 'GB'],
'GB': ['DE', 'NL', 'SE', 'FI']
};
return nearbyMap[region] || [];
}
function getAllRegionsByPriority(region) {
const nearbyRegions = getNearbyRegions(region);
const allRegions = ['US', 'SG', 'JP', 'HK', 'KR', 'DE', 'SE', 'NL', 'FI', 'GB'];
return [region, ...nearbyRegions, ...allRegions.filter(r => r !== region && !nearbyRegions.includes(r))];
}
function getSmartRegionSelection(workerRegion, availableIPs) {
if (!enableRegionMatching || !workerRegion) return availableIPs;
const priorityRegions = getAllRegionsByPriority(workerRegion);
const sortedIPs = [];
for (const region of priorityRegions) {
const regionIPs = availableIPs.filter(ip => ip.regionCode === region);
sortedIPs.push(...regionIPs);
}
return sortedIPs;
}
async function getBestBackupIP(workerRegion = '') {
if (backupIPs.length === 0) return null;
const availableIPs = backupIPs.map(ip => ({ ...ip, available: true }));
if (enableRegionMatching && workerRegion) {
const sortedIPs = getSmartRegionSelection(workerRegion, availableIPs);
if (sortedIPs.length > 0) return sortedIPs[0];
}
return availableIPs[0];
}
function parseAddressAndPort(input) {
if (!input) return { address: '', port: null };
if (input.includes('[') && input.includes(']')) {
const match = input.match(/^\[([^\]]+)\](?::(\d+))?$/);
if (match) {
return { address: match[1], port: match[2] ? parseInt(match[2], 10) : null };
}
}
const lastColonIndex = input.lastIndexOf(':');
if (lastColonIndex > 0) {
const address = input.substring(0, lastColonIndex);
const portStr = input.substring(lastColonIndex + 1);
const port = parseInt(portStr, 10);
if (!isNaN(port) && port > 0 && port <= 65535) return { address, port };
}
return { address: input, port: null };
}
export default {
async fetch(request, env, ctx) {
try {
const url = new URL(request.url);
if (manualWorkerRegion && manualWorkerRegion.trim()) {
currentWorkerRegion = manualWorkerRegion.trim().toUpperCase();
} else {
currentWorkerRegion = await detectWorkerRegion(request);
}
let currentFallbackAddress = fallbackAddress;
if (!currentFallbackAddress && currentWorkerRegion) {
const bestBackupIP = await getBestBackupIP(currentWorkerRegion);
if (bestBackupIP) currentFallbackAddress = bestBackupIP.domain + ':' + bestBackupIP.port;
}
if (request.headers.get('Upgrade') === 'websocket') {
return await handleWsRequest(request, currentFallbackAddress);
} else if (request.method === 'GET') {
if (url.pathname === '/') {
const successHtml = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>服务正常</title><style>body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background-color:#121212;color:#e0e0e0;text-align:center;}.container{padding:2rem;border-radius:8px;background-color:#1e1e1e;box-shadow:0 4px 6px rgba(0,0,0,0.1);}h1{color:#4caf50;}</style></head><body><div class="container"><h1>✅ 服务正常</h1><p>请继续后面的操作。</p></div></body></html>`;
return new Response(successHtml, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
if (customPath && customPath.trim()) {
const cleanCustomPath = customPath.trim().startsWith('/') ? customPath.trim() : '/' + customPath.trim();
const normalizedCustomPath = cleanCustomPath.endsWith('/') && cleanCustomPath.length > 1 ? cleanCustomPath.slice(0, -1) : cleanCustomPath;
const normalizedPath = url.pathname.endsWith('/') && url.pathname.length > 1 ? url.pathname.slice(0, -1) : url.pathname;
if (normalizedPath === normalizedCustomPath) {
return await handleSubscriptionPage(request, authToken);
}
if (normalizedPath === normalizedCustomPath + '/sub') {
return await handleSubscriptionRequest(request, authToken, url);
}
if (url.pathname.length > 1 && url.pathname !== '/') {
const user = url.pathname.replace(/\/$/, '').replace('/sub', '').substring(1);
if (isValidFormat(user)) {
return new Response(JSON.stringify({
error: '访问被拒绝',
message: '当前 Worker 已启用自定义路径模式,UUID 访问已禁用'
}), {
status: 403,
headers: { 'Content-Type': 'application/json; charset=utf-8' }
});
}
}
} else {
if (url.pathname.length > 1 && url.pathname !== '/' && !url.pathname.includes('/sub')) {
const uuid = url.pathname.replace(/\/$/, '').substring(1);
if (isValidFormat(uuid)) {
if (uuid === authToken) return await handleSubscriptionPage(request, uuid);
return new Response('UUID错误', { status: 403 });
}
}
if (url.pathname.includes('/sub')) {
const pathParts = url.pathname.split('/');
if (pathParts.length === 2 && pathParts[1] === 'sub') {
const uuid = pathParts[0].substring(1);
if (isValidFormat(uuid)) {
if (uuid === authToken) return await handleSubscriptionRequest(request, uuid, url);
return new Response('UUID错误', { status: 403 });
}
}
}
if (url.pathname.toLowerCase().includes(`/${authToken}`)) {
return await handleSubscriptionRequest(request, authToken);
}
}
}
return new Response('Not Found', { status: 404 });
} catch (err) {
return new Response(err.toString(), { status: 500 });
}
},
};
async function handleSubscriptionPage(request, uuid = null) {
if (!uuid) uuid = authToken;
const pageHtml = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>订阅中心</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:"Courier New",monospace;background:#000;color:#00ff00;min-height:100vh;overflow-x:hidden;position:relative}
.matrix-bg{position:fixed;top:0;left:0;width:100%;height:100%;background:linear-gradient(45deg,#000 0%,#001100 50%,#000 100%);z-index:-1}
.matrix-rain{position:fixed;top:0;left:0;width:100%;height:100%;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,255,0,0.03) 2px,rgba(0,255,0,0.03) 4px);animation:matrix-fall 20s linear infinite;z-index:-1}
@keyframes matrix-fall{0%{transform:translateY(-100%)}100%{transform:translateY(100vh)}}
.container{max-width:900px;margin:0 auto;padding:20px;position:relative;z-index:1}
.header{text-align:center;margin-bottom:40px}
.title{font-size:3rem;font-weight:bold;text-shadow:0 0 10px #00ff00,0 0 20px #00ff00,0 0 30px #00ff00;margin-bottom:10px;animation:matrix-glow 2s ease-in-out infinite alternate}
@keyframes matrix-glow{from{text-shadow:0 0 10px #00ff00,0 0 20px #00ff00,0 0 30px #00ff00}to{text-shadow:0 0 20px #00ff00,0 0 30px #00ff00,0 0 40px #00ff00}}
.subtitle{color:#00aa00;margin-bottom:30px;font-size:1.2rem}
.card{background:rgba(0,20,0,0.8);border:2px solid #00ff00;border-radius:0;padding:30px;margin-bottom:20px;box-shadow:0 0 20px rgba(0,255,0,0.3);position:relative}
.card::before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(45deg,transparent 49%,#00ff00 50%,transparent 51%);opacity:0.1;pointer-events:none}
.card-title{font-size:1.8rem;margin-bottom:20px;color:#00ff00;text-shadow:0 0 5px #00ff00}
.client-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:15px;margin:20px 0}
.client-btn{background:rgba(0,20,0,0.6);border:2px solid #00ff00;padding:15px 20px;color:#00ff00;font-family:"Courier New",monospace;font-weight:bold;cursor:pointer;transition:all 0.3s ease;text-align:center;position:relative;overflow:hidden}
.client-btn::before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(0,255,0,0.3),transparent);transition:left 0.5s ease}
.client-btn:hover::before{left:100%}
.client-btn:hover{background:rgba(0,255,0,0.2);box-shadow:0 0 15px #00ff00;transform:translateY(-2px)}
.generate-btn{background:rgba(0,255,0,0.1);border:2px solid #00ff00;padding:15px 30px;color:#00ff00;font-family:"Courier New",monospace;font-weight:bold;cursor:pointer;transition:all 0.3s ease;margin-right:15px}
.generate-btn:hover{background:rgba(0,255,0,0.3);box-shadow:0 0 20px #00ff00;transform:translateY(-2px)}
.subscription-url{background:rgba(0,0,0,0.8);border:1px solid #00ff00;padding:15px;word-break:break-all;font-family:"Courier New",monospace;color:#00ff00;margin-top:20px;display:none;box-shadow:inset 0 0 10px rgba(0,255,0,0.3)}
.matrix-text{position:fixed;top:20px;right:20px;color:#00ff00;font-family:"Courier New",monospace;font-size:0.8rem;opacity:0.6;animation:matrix-flicker 3s infinite}
@keyframes matrix-flicker{0%,100%{opacity:0.6}50%{opacity:1}}
</style>
</head>
<body>
<div class="matrix-bg"></div>
<div class="matrix-rain"></div>
<div class="matrix-text">代理订阅中心精简版 v2.1</div>
<div class="container">
<div class="header">
<h1 class="title">代理订阅中心</h1>
<p class="subtitle">多客户端支持 • 智能优选 • 一键生成</p>
</div>
<div class="card">
<h2 class="card-title">[ 选择客户端 ]</h2>
<div class="client-grid">
<button class="client-btn" onclick="generateClientLink('clash','CLASH')">CLASH</button>
<button class="client-btn" onclick="generateClientLink('surge','SURGE')">SURGE</button>
<button class="client-btn" onclick="generateClientLink('singbox','SING-BOX')">SING-BOX</button>
<button class="client-btn" onclick="generateClientLink('loon','LOON')">LOON</button>
<button class="client-btn" onclick="generateClientLink('quanx','QUANTUMULT X')">QUANTUMULT X</button>
<button class="client-btn" onclick="generateClientLink('v2ray','V2RAY')">V2RAY</button>
<button class="client-btn" onclick="generateClientLink('v2ray','Shadowrocket')">Shadowrocket</button>
<button class="client-btn" onclick="generateClientLink('v2ray','V2RAYNG')">V2RAYNG</button>
<button class="client-btn" onclick="generateClientLink('v2ray','NEKORAY')">NEKORAY</button>
<button class="client-btn" onclick="generateClientLink('clash','STASH')">STASH</button>
</div>
<div class="subscription-url" id="clientSubscriptionUrl"></div>
</div>
<div class="card">
<h2 class="card-title">[ 快速获取 ]</h2>
<button class="generate-btn" onclick="getBase64Subscription()">获取订阅链接</button>
<div class="subscription-url" id="base64SubscriptionUrl"></div>
</div>
<div class="card">
<h2 class="card-title">[ 相关链接 ]</h2>
<div style="text-align:center;margin:20px 0">
<a href="https://github.com/byJoey/cfnew" target="_blank" style="color:#00ff00;text-decoration:none;margin:0 20px;font-size:1.2rem;text-shadow:0 0 5px #00ff00">GitHub 项目</a>
<a href="https://www.youtube.com/@joeyblog" target="_blank" style="color:#00ff00;text-decoration:none;margin:0 20px;font-size:1.2rem;text-shadow:0 0 5px #00ff00">YouTube @joeyblog</a>
</div>
</div>
</div>
<script>
var SUB_CONVERTER_URL="${apiBaseUrl}";
function tryOpenApp(schemeUrl,fallbackCallback,timeout){
timeout=timeout||2500;
var appOpened=false;
var callbackExecuted=false;
var startTime=Date.now();
var blurHandler=function(){
var elapsed=Date.now()-startTime;
if(elapsed<3000&&!callbackExecuted){appOpened=true;}
};
window.addEventListener('blur',blurHandler);
var hiddenHandler=function(){
var elapsed=Date.now()-startTime;
if(elapsed<3000&&!callbackExecuted){appOpened=true;}
};
document.addEventListener('visibilitychange',hiddenHandler);
var iframe=document.createElement('iframe');
iframe.style.display='none';
iframe.style.width='1px';
iframe.style.height='1px';
iframe.src=schemeUrl;
document.body.appendChild(iframe);
setTimeout(function(){
if(iframe.parentNode)iframe.parentNode.removeChild(iframe);
window.removeEventListener('blur',blurHandler);
document.removeEventListener('visibilitychange',hiddenHandler);
if(!callbackExecuted){
callbackExecuted=true;
if(!appOpened&&fallbackCallback)fallbackCallback();
}
},timeout);
}
function generateClientLink(clientType,clientName){
var currentUrl=window.location.href;
var subscriptionUrl=currentUrl+"/sub";
var schemeUrl='';
var displayName=clientName||'';
var finalUrl=subscriptionUrl;
if(clientType==='v2ray'){
finalUrl=subscriptionUrl;
document.getElementById("clientSubscriptionUrl").textContent=finalUrl;
document.getElementById("clientSubscriptionUrl").style.display="block";
if(clientName==='V2RAY'){
navigator.clipboard.writeText(finalUrl).then(function(){alert(displayName+" 订阅链接已复制");});
}else if(clientName==='Shadowrocket'){
schemeUrl='shadowrocket://add/'+encodeURIComponent(finalUrl);
tryOpenApp(schemeUrl,function(){
navigator.clipboard.writeText(finalUrl).then(function(){alert(displayName+" 订阅链接已复制");});
});
}else if(clientName==='V2RAYNG'){
schemeUrl='v2rayng://install?url='+encodeURIComponent(finalUrl);
tryOpenApp(schemeUrl,function(){
navigator.clipboard.writeText(finalUrl).then(function(){alert(displayName+" 订阅链接已复制");});
});
}else if(clientName==='NEKORAY'){
schemeUrl='nekoray://install-config?url='+encodeURIComponent(finalUrl);
tryOpenApp(schemeUrl,function(){
navigator.clipboard.writeText(finalUrl).then(function(){alert(displayName+" 订阅链接已复制");});
});
}
}else{
var encodedUrl=encodeURIComponent(subscriptionUrl);
finalUrl=SUB_CONVERTER_URL+"?target="+clientType+"&url="+encodedUrl+"&insert=false";
document.getElementById("clientSubscriptionUrl").textContent=finalUrl;
document.getElementById("clientSubscriptionUrl").style.display="block";
if(clientType==='clash'){
if(clientName==='STASH'){
schemeUrl='stash://install?url='+encodeURIComponent(finalUrl);
displayName='STASH';
}else{
schemeUrl='clash://install-config?url='+encodeURIComponent(finalUrl);
displayName='CLASH';
}
}else if(clientType==='surge'){
schemeUrl='surge:///install-config?url='+encodeURIComponent(finalUrl);
displayName='SURGE';
}else if(clientType==='singbox'){
schemeUrl='sing-box://install-config?url='+encodeURIComponent(finalUrl);
displayName='SING-BOX';
}else if(clientType==='loon'){
schemeUrl='loon://install?url='+encodeURIComponent(finalUrl);
displayName='LOON';
}else if(clientType==='quanx'){
schemeUrl='quantumult-x://install-config?url='+encodeURIComponent(finalUrl);
displayName='QUANTUMULT X';
}
if(schemeUrl){
tryOpenApp(schemeUrl,function(){
navigator.clipboard.writeText(finalUrl).then(function(){alert(displayName+" 订阅链接已复制");});
});
}else{
navigator.clipboard.writeText(finalUrl).then(function(){alert(displayName+" 订阅链接已复制");});
}
}
}
function getBase64Subscription(){
var currentUrl=window.location.href;
var subscriptionUrl=currentUrl+"/sub";
fetch(subscriptionUrl).then(function(response){return response.text();}).then(function(base64Content){
document.getElementById("base64SubscriptionUrl").textContent=base64Content;
document.getElementById("base64SubscriptionUrl").style.display="block";
navigator.clipboard.writeText(base64Content).then(function(){alert("Base64订阅内容已复制");});
}).catch(function(error){alert("获取订阅失败,请重试");});
}
</script>
</body>
</html>`;
return new Response(pageHtml, {
status: 200,
headers: { 'Content-Type': 'text/html; charset=utf-8' }
});
}
async function handleSubscriptionRequest(request, uuid, url = null) {
if (!url) url = new URL(request.url);
const finalLinks = [];
const workerDomain = url.hostname;
const nativeList = [{ ip: workerDomain, isp: '原生地址' }];
finalLinks.push(...generateLinksFromSource(nativeList, uuid, workerDomain));
if (enableOtherPreferred) {
const domainList = directDomains.map(d => ({ ip: d.domain, isp: d.name || d.domain }));
finalLinks.push(...generateLinksFromSource(domainList, uuid, workerDomain));
}
if (enableGitHubPreferred) {
const newIPList = await fetchAndParseNewIPs();
if (newIPList.length > 0) finalLinks.push(...generateLinksFromNewIPs(newIPList, uuid, workerDomain));
}
if (finalLinks.length === 0) {
const errorRemark = "所有节点获取失败";
const errorLink = `vless://00000000-0000-0000-0000-000000000000@127.0.0.1:80?encryption=none&security=none&type=ws&host=error.com&path=%2F#${encodeURIComponent(errorRemark)}`;
finalLinks.push(errorLink);
}
const subscriptionContent = btoa(finalLinks.join('\n'));
return new Response(subscriptionContent, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
},
});
}
function generateLinksFromSource(list, uuid, workerDomain) {
const httpsPorts = [443];
const links = [];
const wsPath = '/?ed=2048';
const proto = 'vless';
list.forEach(item => {
const nodeNameBase = item.isp.replace(/\s/g, '_');
const safeIP = item.ip.includes(':') ? `[${item.ip}]` : item.ip;
httpsPorts.forEach(port => {
const wsNodeName = `${nodeNameBase}-${port}-WS-TLS`;
const wsParams = new URLSearchParams({
encryption: 'none',
security: 'tls',
sni: workerDomain,
fp: 'randomized',
type: 'ws',
host: workerDomain,
path: wsPath
});
links.push(`${proto}://${uuid}@${safeIP}:${port}?${wsParams.toString()}#${encodeURIComponent(wsNodeName)}`);
});
});
return links;
}
async function fetchAndParseNewIPs() {
const url = githubPreferredURL;
try {
const response = await fetch(url);
if (!response.ok) return [];
const text = await response.text();
const results = [];
const lines = text.trim().replace(/\r/g, "").split('\n');
const regex = /^([^:]+):(\d+)#(.*)$/;
for (const line of lines) {
const trimmedLine = line.trim();
if (!trimmedLine) continue;
const match = trimmedLine.match(regex);
if (match) {
results.push({
ip: match[1],
port: parseInt(match[2], 10),
name: match[3].trim() || match[1]
});
}
}
return results;
} catch (error) {
return [];
}
}
function generateLinksFromNewIPs(list, uuid, workerDomain) {
const links = [];
const wsPath = '/?ed=2048';
const proto = 'vless';
list.forEach(item => {
const nodeName = item.name;
const safeIP = item.ip.includes(':') ? `[${item.ip}]` : item.ip;
const params = {
encryption: 'none',
security: 'tls',
sni: workerDomain,
fp: 'randomized',
type: 'ws',
host: workerDomain,
path: wsPath
};
const wsParams = new URLSearchParams(params);
links.push(`${proto}://${uuid}@${safeIP}:${item.port}?${wsParams.toString()}#${encodeURIComponent(nodeName)}`);
});
return links;
}
async function handleWsRequest(request, currentFallbackAddress = null) {
const wsPair = new WebSocketPair();
const [clientSock, serverSock] = Object.values(wsPair);
serverSock.accept();
let remoteConnWrapper = { socket: null };
let isDnsQuery = false;
const fbAddr = currentFallbackAddress || fallbackAddress;
const earlyData = request.headers.get('sec-websocket-protocol') || '';
const readable = makeReadableStream(serverSock, earlyData);
readable.pipeTo(new WritableStream({
async write(chunk) {
if (isDnsQuery) return await forwardUDP(chunk, serverSock, null);
if (remoteConnWrapper.socket) {
const writer = remoteConnWrapper.socket.writable.getWriter();
await writer.write(chunk);
writer.releaseLock();
return;
}
const { hasError, message, addressType, port, hostname, rawIndex, version, isUDP } = parseWsPacketHeader(chunk, authToken);
if (hasError) throw new Error(message);
if (isUDP) {
if (port === 53) isDnsQuery = true;
else throw new Error(E_UDP_DNS_ONLY);
}
const respHeader = new Uint8Array([version[0], 0]);
const rawData = chunk.slice(rawIndex);
if (isDnsQuery) return forwardUDP(rawData, serverSock, respHeader);
await forwardTCP(addressType, hostname, port, rawData, serverSock, respHeader, remoteConnWrapper, fbAddr);
},
})).catch((err) => { console.log('WS Stream Error:', err); });
return new Response(null, { status: 101, webSocket: clientSock });
}
async function forwardTCP(addrType, host, portNum, rawData, ws, respHeader, remoteConnWrapper, fbAddr = null) {
async function connectAndSend(address, port) {
const remoteSock = connect({ hostname: address, port: port });
const writer = remoteSock.writable.getWriter();
await writer.write(rawData);
writer.releaseLock();
return remoteSock;
}
async function retryConnection() {
let fallbackHost, fallbackPort;
if (fbAddr && fbAddr.trim()) {
const parsed = parseAddressAndPort(fbAddr);
fallbackHost = parsed.address;
fallbackPort = parsed.port || portNum;
} else if (fallbackAddress && fallbackAddress.trim()) {
const parsed = parseAddressAndPort(fallbackAddress);
fallbackHost = parsed.address;
fallbackPort = parsed.port || portNum;
} else {
const bestBackupIP = await getBestBackupIP(currentWorkerRegion);
fallbackHost = bestBackupIP ? bestBackupIP.domain : host;
fallbackPort = bestBackupIP ? bestBackupIP.port : portNum;
}
const newSocket = await connectAndSend(fallbackHost || host, fallbackPort);
remoteConnWrapper.socket = newSocket;
newSocket.closed.catch(() => { }).finally(() => closeSocketQuietly(ws));
connectStreams(newSocket, ws, respHeader, null);
}
try {
const initialSocket = await connectAndSend(host, portNum);
remoteConnWrapper.socket = initialSocket;
connectStreams(initialSocket, ws, respHeader, retryConnection);
} catch (err) {
console.log('Initial connection failed, trying fallback:', err);
retryConnection();
}
}
function parseWsPacketHeader(chunk, token) {
if (chunk.byteLength < 24) return { hasError: true, message: E_INVALID_DATA };
const version = new Uint8Array(chunk.slice(0, 1));
if (formatIdentifier(new Uint8Array(chunk.slice(1, 17))) !== token) return { hasError: true, message: E_INVALID_USER };
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: E_UNSUPPORTED_CMD }; }
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 ADDRESS_TYPE_IPV4:
addrLen = 4;
hostname = new Uint8Array(chunk.slice(addrValIdx, addrValIdx + addrLen)).join('.');
break;
case ADDRESS_TYPE_URL:
addrLen = new Uint8Array(chunk.slice(addrValIdx, addrValIdx + 1))[0];
addrValIdx += 1;
hostname = new TextDecoder().decode(chunk.slice(addrValIdx, addrValIdx + addrLen));
break;
case ADDRESS_TYPE_IPV6:
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: `${E_INVALID_ADDR_TYPE}: ${addressType}` };
}
if (!hostname) return { hasError: true, message: `${E_EMPTY_ADDR}: ${addressType}` };
return { hasError: false, addressType, port, hostname, isUDP, rawIndex: addrValIdx + addrLen, version };
}
function makeReadableStream(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); }
});
}
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 !== 1) controller.error(E_WS_NOT_OPEN);
if (header) {
webSocket.send(await new Blob([header, chunk]).arrayBuffer());
header = null;
} else {
webSocket.send(chunk);
}
},
abort(reason) { console.error("Readable aborted:", reason); },
})
).catch((error) => { console.error("Stream connection error:", error); closeSocketQuietly(webSocket); });
if (!hasData && retryFunc) retryFunc();
}
async function forwardUDP(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 === 1) {
if (vlessHeader) {
webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer());
vlessHeader = null;
} else {
webSocket.send(chunk);
}
}
},
}));
} catch (error) {
console.error(`DNS forward error: ${error.message}`);
}
}
function base64ToArray(b64Str) {
if (!b64Str) return { error: null };
try {
b64Str = b64Str.replace(/-/g, '+').replace(/_/g, '/');
return { earlyData: Uint8Array.from(atob(b64Str), (c) => c.charCodeAt(0)).buffer, error: null };
} catch (error) {
return { error };
}
}
function isValidFormat(uuid) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid);
}
function closeSocketQuietly(socket) {
try { if (socket.readyState === 1 || socket.readyState === 2) socket.close(); }
catch (error) { }
}
const hexTable = Array.from({ length: 256 }, (v, i) => (i + 256).toString(16).slice(1));
function formatIdentifier(arr, offset = 0) {
const id = (
hexTable[arr[offset]] + hexTable[arr[offset + 1]] + hexTable[arr[offset + 2]] + hexTable[arr[offset + 3]] + "-" +
hexTable[arr[offset + 4]] + hexTable[arr[offset + 5]] + "-" +
hexTable[arr[offset + 6]] + hexTable[arr[offset + 7]] + "-" +
hexTable[arr[offset + 8]] + hexTable[arr[offset + 9]] + "-" +
hexTable[arr[offset + 10]] + hexTable[arr[offset + 11]] + hexTable[arr[offset + 12]] + hexTable[arr[offset + 13]] +
hexTable[arr[offset + 14]] + hexTable[arr[offset + 15]]
).toLowerCase();
if (!isValidFormat(id)) throw new TypeError(E_INVALID_ID_STR);
return id;
}
```
## /wrangler.toml
```toml path="/wrangler.toml"
compatibility_date = "2026-01-20"
```
## /ÃÂçñóÃÂ.md
# CFnew - ترمینال v2.9.8b
**زبان:** [中文](README.md) | [فارسی](فارسی.md)
[Telegram گروه](https://t.me/+ft-zI76oovgwNmRh)
## ویژگیهای اصلی
- پشتیبانی چند پروتکل: VLESS، Trojan، xhttp، میتونید همزمان چند تا رو فعال کنید
- مسیر سفارشی: دیگه از UUID به عنوان مسیر استفاده نمیشه، میتونید خودتون تنظیم کنید، پشتیبانی از مسیرهای چند سطحی
- تست تاخیر: ابزار تست داخلی، تست تاخیر IP، دریافت خودکار کد فرودگاه
- تبدیل اشتراک: میتونید آدرس سرویس تبدیل رو خودتون تنظیم کنید
- مدیریت گرافیکی: با KV ذخیره میشه، تغییرات بلافاصله اعمال میشه، نیازی به استقرار مجدد نیست
- مدیریت API: از طریق RESTful API میتونید IP ترجیحی رو مدیریت کنید
- پشتیبانی چند کلاینت: CLASH، SURGE، SING-BOX، LOON، QUANTUMULT X، V2RAY، Shadowrocket، STASH، NEKORAY، V2RAYNG
- بیدار کردن برنامه: با کلیک روی دکمه خودکار برنامه کلاینت باز میشه
- تشخیص خودکار: بر اساس User-Agent خودکار تشخیص میده و قالب مناسب رو برمیگردونه
- پشتیبانی چند زبان: پشتیبانی از چینی و فارسی، بر اساس زبان مرورگر خودکار تغییر میکنه
## بهروزرسانی v2.9.8b
- تبدیل اشتراک داخلی: Clash / Stash / Sing-box / Surge / Loon / Quantumult X بدون sub-converter خارجی
- مجموعه قوانین کامل (Loyalsoldier / MetaCubeX / ACL4SSR / blackmatrix7)
- هر گروه سیاست شامل «گروههای استراتژی + تمام گرهها»
- رفع مشکل IPv6 در Clash، حلقه گروه پروکسی، و موارد مشابه
- بهینهسازی انتقال با الهام از GrainTCP: ادغام بستههای کوچک در مسیر آپلود، تجمیع بستههای کوچک در دانلود، ارسال مستقیم بستههای بزرگ، و بهینهسازی مسیر داغ تحلیل VLESS
- ALPN گرافیکی: گزینه `alpn` اضافه شد؛ اگر خالی باشد پارامتر `alpn` نوشته نمیشود، و میتوانید `h3`، `h2`، `http/1.1` یا ترکیبها را انتخاب کنید
- نام مستعار گرهها ساده شد: دامنهها `优选域名-شماره`، IPv6ها `IPv6优选-شماره`، و IPv4ها `isp-colo-شماره`
- کش KV با `c_ver`؛ تایماوت SOCKS5؛ برچسب «ترجیح سفارشی»؛ کلید FX؛ نسخه مبهم `少年你相信光吗`
## بهروزرسانی v2.9.7
- دکمه ذخیره شناور در گوشه پایین-راست با میانبر `Ctrl+S` / `Cmd+S`
- بعد از ویرایش هر فیلد، دکمه بهطور خودکار وارد حالت «ذخیرهنشده» میشود
- بازخورد بصری هنگام ذخیره / بازخوانی
- بهبود تجربه اعلانها: تمام پنجرههای مسدودکننده با پیامهای شناور در گوشه بالا-راست جایگزین شدند — حذف خودکار، توقف با hover و امکان بستن دستی
- ۴ نوع: success / info / warn / error
- یکپارچهسازی دکمههای عملیات: چهار دکمه ذخیره پراکنده در بخشهای مختلف به یک گروه شناور واحد ادغام شدند
- نسخه مبهمشده `少年你相信光吗` ارائه شده، رفتار با `明文源吗` کاملاً یکسان
## بهروزرسانی v2.9.6
- سازگاری با Xray-core v26.3.27
- بهینهسازی عملکرد خواندن KV: کش حافظه ۵ ساعته، کاهش بیش از ۹۹٪ خواندن KV
- مسدودسازی درخواستهای نامعتبر: مسیرهای غیرمجاز مستقیماً ۴۰۴ برمیگردانند
- رفع مشکل ذخیرهسازی کلید تنظیمات SOCKS5 هنگام ذخیره لیست ترجیحی
## بهروزرسانی v2.9.3
- ویژگی جدید سفارشیسازی DNS و دامنه ECH در رابط گرافیکی
- میتونید آدرس سرور DNS رو خودتون تنظیم کنید (فرمت DoH)
- میتونید دامنه ECH رو خودتون تنظیم کنید
- پشتیبانی از تغییرات پویا، بعد از ذخیره بلافاصله اعمال میشه
- پارامتر query-server-name به ech-opts در پیکربندی Clash اضافه شد، با v2ray هماهنگ شد
## بهروزرسانی v2.9
- فیلتر منطقه: میتونید نتایج ترجیحی رو بر اساس منطقه فیلتر کنید، پشتیبانی از چند انتخاب
- فیلتر تاخیر: گزینه جدید "فقط نمایش 10 تا سریعترین"
- حالت اضافه/جایگزین: هنگام افزودن نتایج ترجیحی میتونید اضافه کنید یا کل لیست رو جایگزین کنید
- بهینهسازی نمایش نتایج: نمایش برچسب منطقه، مرتبسازی بر اساس تاخیر
- بهینهسازیهای جزئی دیگر
---
### ابزارهای مرتبط
- ابزار ترجیحی: https://github.com/byJoey/yx-tools/releases
- آموزش متنی: https://joeyblog.net/yuanchuang/1146.html
- آموزش ویدیویی Workers: https://www.youtube.com/watch?v=aYzTr8FafN4
- آموزش ویدیویی Pages: https://www.youtube.com/watch?v=JhVxJChDL-E
- آموزش ویدیویی Snippets: https://www.youtube.com/watch?v=xeFeH3Akcu8
### استقرار
اشتراک هر 15 دقیقه یکبار خودکار انتخاب میشه
#### پیکربندی پایه
| نام متغیر | مقدار | توضیحات |
| :--- | :--- | :--- |
| `u` | UUID شما | الزامی، برای دسترسی به اشتراک و رابط مدیریت |
| `p` | proxyip | اختیاری، آدرس و پورت ProxyIP سفارشی |
| `s` | آدرس SOCKS5 شما | اختیاری، فرمت: `user:pass@host:port` یا `host:port` |
| `d` | مسیر سفارشی | اختیاری، مثل `/mypath` یا `/path/to/sub`، اگر خالی بذارید از UUID استفاده میشه. اگر مسیر با `/` شروع نشه خودکار اضافه میشه |
| `wk` | کد منطقه | اختیاری، مثل `SG`، `HK`، `US`، `JP` |
#### پیکربندی پروتکل
| نام متغیر | مقدار | توضیحات |
| :--- | :--- | :--- |
| `ev` | yes/no | اختیاری، فعالسازی VLESS (پیشفرض فعال) |
| `et` | yes/no | اختیاری، فعالسازی Trojan (پیشفرض غیرفعال) |
| `ex` | yes/no | اختیاری، فعالسازی xhttp (پیشفرض غیرفعال) |
| `tp` | رمز عبور سفارشی | اختیاری، رمز عبور Trojan، خالی بذارید از UUID استفاده میشه |
| `ech` | yes/no | اختیاری، فعالسازی ECH (پیشفرض غیرفعال) |
| `alpn` | لیست ALPN | اختیاری، فقط برای گرههای TLS. اگر خالی باشد نوشته نمیشود و کلاینت مذاکره میکند؛ گزینهها: `h3`، `h2`، `http/1.1`، `h3,h2`، `h2,http/1.1`، `h3,h2,http/1.1` |
#### پیکربندی گرافیکی (توصیه میشه)
1. در Workers یک فضای نام KV ایجاد کنید، متغیر محیطی `C` رو متصل کنید
2. بعد از استقرار به `/{UUID شما}` برید تا از رابط گرافیکی استفاده کنید
3. تغییرات بلافاصله اعمال میشه، نیازی به استقرار مجدد نیست
#### کنترل پیشرفته
| نام متغیر | مقدار | توضیحات |
| :--- | :--- | :--- |
| `yx` | IP/دامنه ترجیحی سفارشی | اختیاری، پشتیبانی از نامگذاری، فرمت: `1.1.1.1:443#گره هنگکنگ,8.8.8.8:53#Google DNS` |
| `yxURL` | URL منبع IP ترجیحی | اختیاری، اگر خالی بذارید از آدرس پیشفرض استفاده میشه |
| `scu` | آدرس تبدیل اشتراک | اختیاری، پیشفرض: `https://url.v1.mk/sub` |
| `epd` | yes/no | اختیاری، فعالسازی دامنه ترجیحی (پیشفرض فعال) |
| `epi` | yes/no | اختیاری، فعالسازی IP ترجیحی (پیشفرض فعال) |
| `egi` | yes/no | اختیاری، فعالسازی ترجیح GitHub (پیشفرض فعال) |
| `qj` | no | اختیاری، وقتی `no` باشه حالت کاهش سطح فعال میشه: CF مستقیم ناموفق → SOCKS5 → fallback |
| `dkby` | yes | اختیاری، وقتی `yes` باشه فقط گرههای TLS تولید میشه |
| `ech` | yes/no | اختیاری، فعالسازی ECH (با فعالسازی، حالت فقط TLS خودکار روشن میشود) |
| `alpn` | لیست ALPN | اختیاری، فقط در پارامتر لینک گرههای TLS نوشته میشود؛ خالی یعنی نوشته نشود |
| `yxby` | yes | اختیاری، وقتی `yes` باشه تمام عملکردهای ترجیحی خاموش میشه |
| `rm` | no | اختیاری، وقتی `no` باشه تطبیق هوشمند منطقه خاموش میشه |
| `ae` | yes | اختیاری، وقتی `yes` باشه اجازه مدیریت API داده میشه (پیشفرض خاموش) |
#### تنظیمات KV (توصیه میشه)
1. در Cloudflare Workers یک فضای نام KV ایجاد کنید
2. در تنظیمات Workers KV رو متصل کنید، نام متغیر رو `C` بذارید
3. Workers رو دوباره استقرار بدید
4. به `/{UUID شما}` برید تا از رابط گرافیکی استفاده کنید
#### استفاده از API
1. نرمافزار ترجیحی: https://github.com/byJoey/yx-tools/releases
2. فعالسازی API: به `/{UUID}` یا `/{مسیر سفارشی}` برید، "اجازه مدیریت API" رو پیدا کنید، فعال کنید و ذخیره کنید
3. افزودن IP تک:
```bash
# استفاده از مسیر UUID
curl -X POST "https://your-worker.workers.dev/{UUID}/api/preferred-ips" \
-H "Content-Type: application/json" \
-d '{"ip": "1.2.3.4", "port": 443, "name": "گره هنگکنگ"}'
# استفاده از مسیر سفارشی (اگر متغیر d تنظیم شده باشد)
curl -X POST "https://your-worker.workers.dev/{مسیر سفارشی}/api/preferred-ips" \
-H "Content-Type: application/json" \
-d '{"ip": "1.2.3.4", "port": 443, "name": "گره هنگکنگ"}'
```
4. افزودن دستهای IP:
```bash
curl -X POST "https://your-worker.workers.dev/{UUID یا مسیر سفارشی}/api/preferred-ips" \
-H "Content-Type: application/json" \
-d '[
{"ip": "1.2.3.4", "port": 443, "name": "گره 1"},
{"ip": "5.6.7.8", "port": 8443, "name": "گره 2"}
]'
```
5. پاک کردن همه IP:
```bash
curl -X DELETE "https://your-worker.workers.dev/{UUID یا مسیر سفارشی}/api/preferred-ips" \
-H "Content-Type: application/json" \
-d '{"all": true}'
```
### توضیحات عملکرد
#### تست تاخیر
از v2.7 شروع شد، v2.9 فیلتر رو تقویت کرد
- ابزار تست داخلی، نیازی به نصب نرمافزار دیگه نیست، مستقیماً در صفحه پیکربندی تست تاخیر IP
- منابع IP:
- ورودی دستی: مستقیماً IP یا دامنه وارد کنید، پشتیبانی از دستهای (با کاما جدا کنید)
- IP تصادفی CF: از محدوده IP Cloudflare به صورت تصادفی تولید میشه
- دریافت از URL: از URL راهدور لیست IP رو دریافت میکنه
- پشتیبانی از تست همزمان 1-50 رشته، پیشفرض 5 رشته
- دریافت خودکار کد فرودگاه (مثل SJC، LAX)
- نگاشت خودکار نام فرودگاه چینی (SJC→سنخوزه)
- کسر خودکار زمان DNS+TLS، نمایش تاخیر واقعی
- تنظیمات خودکار در مرورگر ذخیره میشه
- پشتیبانی از فیلتر بر اساس منطقه
- پشتیبانی از نمایش فقط 10 تا سریعترین
- پشتیبانی از حالت اضافه یا جایگزین
#### پشتیبانی چند پروتکل
- VLESS: پیشفرض فعال
- Trojan: پشتیبانی از Trojan-WS-TLS، میتونید رمز عبور سفارشی بذارید، خالی بذارید از UUID استفاده میشه
- xhttp: پروتکل استتار مبتنی بر HTTP POST
- میتونید همزمان چند پروتکل رو فعال کنید، کلاینت خودکار تشخیص میده
- در رابط گرافیکی میتونید با یک کلیک فعال/غیرفعال کنید
- دکمه ذخیره مستقل برای پیکربندی پروتکل
#### مسیر سفارشی (متغیر d)
- دیگه از UUID به عنوان مسیر استفاده نمیشه، میتونید خودتون تنظیم کنید
- پشتیبانی از مسیرهای چند سطحی، مثل `/path/to/sub`
- اگر مسیر با `/` شروع نشه خودکار اضافه میشه
- بعد از مسیر سفارشی مسیر UUID خودکار غیرفعال میشه
- میتونید هر زمان از رابط گرافیکی مسیر رو تغییر بدید
#### مدیریت پیکربندی گرافیکی
- از Cloudflare KV برای ذخیره پیکربندی استفاده میشه
- به `/{UUID شما}` یا `/{مسیر سفارشی}` برید تا از رابط استفاده کنید
- تغییرات بلافاصله اعمال میشه، نیازی به استقرار مجدد نیست
- اولویت: پیکربندی KV > متغیر محیطی > مقدار پیشفرض
#### پشتیبانی چند زبان
- بر اساس زبان مرورگر خودکار چینی یا فارسی رو انتخاب میکنه
- در گوشه بالا راست میتونید دستی تغییر بدید
- انتخاب زبان در مرورگر ذخیره میشه
- نسخه فارسی خودکار RTL رو فعال میکنه
#### کنترل تبدیل اشتراک
- میتونید آدرس سرویس تبدیل رو خودتون تنظیم کنید
- میتونید دامنه ترجیحی، IP ترجیحی، ترجیح GitHub رو جداگانه کنترل کنید
- پیشفرض همه فعال هستند
- تغییرات بلافاصله اعمال میشه
#### مدیریت API
- از طریق RESTful API میتونید IP ترجیحی رو مدیریت کنید، نیازی به تغییر کد نیست
- پشتیبانی از افزودن دستهای
- پشتیبانی از پاک کردن همه IP
- پیشفرض خاموشه، باید در رابط گرافیکی فعال کنید
- IP های اضافه شده از طریق API با متغیر yx خودکار ادغام میشه
- API endpoints:
- `GET /{UUID یا مسیر}/api/preferred-ips` - پرسوجوی لیست
- `POST /{UUID یا مسیر}/api/preferred-ips` - افزودن (تک/دستهای)
- `DELETE /{UUID یا مسیر}/api/preferred-ips` - حذف (تک/همه)
#### تعیین دستی منطقه
- میتونید دستی منطقه Worker رو تنظیم کنید، تشخیص خودکار رو بازنویسی میکنه
- تنظیم: `wk=SG` یا از رابط گرافیکی
- پشتیبانی از: US، SG، JP، HK، KR، DE، SE، NL، FI، GB
#### نامگذاری گره ترجیحی
- نام مستعار اشتراک بهصورت کوتاه تولید میشود و دیگر پورت، پروتکل، TLS/WS و اطلاعات اضافی به آن اضافه نمیشود
- گره دامنه: `优选域名-01`، `优选域名-02`
- گره IPv6: `IPv6优选-01`، `IPv6优选-02`
- گره IPv4: در اولویت با `isp-colo-شماره` ساخته میشود؛ اگر اطلاعات اپراتور نباشد به `IPv4优选-شماره` برمیگردد
#### وضعیت سیستم
- نمایش منطقه Worker، روش تشخیص، وضعیت ProxyIP
- منطق انتخاب: هممنطقه → منطقه مجاور → سایر مناطق
#### کنترل پیشرفته
- `rm=no` خاموش کردن تطبیق هوشمند منطقه
- `qj=no` فعالسازی حالت کاهش سطح (CF مستقیم ناموفق → SOCKS5 → fallback)
- `dkby=yes` فقط تولید گرههای TLS
- `ech=yes` فعالسازی ECH (بعد از فعالسازی، حالت فقط TLS خودکار روشن میشود)
- `alpn=h3,h2` تعیین ALPN برای گرههای TLS؛ اگر خالی باشد نوشته نمیشود
- `yxby=yes` خاموش کردن تمام عملکردهای ترجیحی
#### پشتیبانی چند کلاینت
پشتیبانی از 10 کلاینت: CLASH، SURGE، SING-BOX، LOON، QUANTUMULT X، V2RAY، Shadowrocket، STASH، NEKORAY، V2RAYNG
- بر اساس نوع کلاینت خودکار پیکربندی تولید میشه
- در رابط گرافیکی میتونید یک کلیکی لینک اشتراک بگیرید
- با کلیک روی دکمه خودکار برنامه کلاینت باز میشه
- بر اساس User-Agent خودکار تشخیص میده
- کلاینتهای مختلف خودکار با بهترین ترکیب پروتکل سازگار میشه
- ALPN لینکهای TLS بهصورت پیشفرض خالی میماند؛ در رابط گرافیکی یا با `alpn` قابل تنظیم است
#### بهینهسازی عملکرد
- هر 15 دقیقه یکبار خودکار انتخاب میشه
- چندین طرح پشتیبان
- کش هوشمند، کاهش محاسبات تکراری
### تشکر
- بر اساس [zizifn/edgetunnel](https://github.com/zizifn/edgetunnel) اصلاح شده
- ProxyIP از [cmliu](https://github.com/cmliu)
- IP پروکسی معکوس از [qwer-search](https://github.com/qwer-search)
- رابط ترجیح آنلاین از [白嫖哥](https://t.me/bestcfipas)
## تاریخچه ستاره
[](https://www.star-history.com/#byJoey/cfnew&Timeline&LogScale)
The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.