Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 119 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,146 @@
# Cloudflare Workers Subscription Converter & SOCKS5 Chain Proxy (cf-cub)
# 🚀 多客户端订阅转换器与链式代理(`cf-sub`)

<p align="center">
<a href="https://workers.cloudflare.com/" target="_blank">
<img src="https://img.shields.io/badge/Cloudflare-Workers-F38020?style=flat-square&logo=cloudflare&logoColor=white" alt="Cloudflare Workers" />
</a>
<a href="https://www.typescriptlang.org/" target="_blank">
<img src="https://img.shields.io/badge/TypeScript-5.7-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript" />
</a>
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-MIT-4CAF50?style=flat-square&logo=open-source-initiative&logoColor=white" alt="License" />
</a>
<a href="https://github.com/Mareixcode/cf-sub">
<img src="https://img.shields.io/github/stars/Mareixcode/cf-sub?style=flat-square&logo=github" alt="GitHub Stars" />
</a>
<a href="https://github.com/Mareixcode/cf-sub/commits/main">
<img src="https://img.shields.io/github/last-commit/Mareixcode/cf-sub?style=flat-square&logo=git&logoColor=white" alt="Last Commit" />
</a>
</p>

> ⚡ 一个多客户端机场订阅转换工具与家宽链式代理节点注入工具

一个部署在 **Cloudflare Workers** 的开源机场订阅转换服务与 SOCKS5 链式代理 Exit 节点注入工具。
---

[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/Mareixcode/cf-cub)
[![License: MIT](https://img.shields.com/badge/License-MIT-blue.svg)](LICENSE)
[![TypeScript](https://img.shields.com/badge/TypeScript-5.7-blue)](https://www.typescriptlang.org/)
[![Cloudflare Workers](https://img.shields.com/badge/Cloudflare-Workers-orange)](https://workers.cloudflare.com/)
## 📖 项目简介

---
`cf-sub` 可以将标准的机场节点订阅转化为支持多客户端格式(**Clash / Mihomo**、**Sing-box**、**Surge**、**Quantumult X**、**Shadowrocket**)的配置。为机场节点自动附加后置家宽代理(支持 SOCKS5, HTTP, HTTPS, SS, Trojan, VLESS 出口),以家宽 IP 作为终点出口,同时保留机场原生节点与原有的分流策略。

## ⚡ 30 秒极简部署
---

选择以下 **任意一种** 方式即可完成部署:
## 🌟 核心特性

### 方式一:一键网页部署 (推荐,无需本地环境)
- 🌐 **多客户端**:
- **Clash / Mihomo** (YAML) -> 利用 `dialer-proxy` 链式代理
- **Sing-box** (JSON 1.8+) -> 利用 `detour` 链式代理
- **Surge** (.conf) -> 利用 `under-proxy` 链式代理
- **Quantumult X** -> 策略组与节点转换
- **Shadowrocket / 通用节点** -> Base64 编码与单行 URI 列表
- 🛡️ **家宽敏感信息零暴露**:支持在 Workers 环境变量中存储家宽 IP、端口与密码。
- 📊 **剩余流量与到期时间显示**:在客户端卡片及 Web UI 中直观展示已用流量、剩余流量及到期倒计时。
- 🔗 **一键客户端导入**:可一键生成并调用 `clash://`, `sing-box://`, `surge://`, `sub://` 客户端快捷导入链接。
---

点击下方按钮,登录 Cloudflare 网页上一键自动完成部署:
## 🏗️ 工作原理

[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/Mareixcode/cf-cub)
```text
[ 用户客户端 (Clash / Sing-box / Surge) ]
▼ (访问目标网站)
[ 前置节点 (机场节点) ] ── (中继流量) ──► [ 出口节点 (家宽出口) ] ──► [ 目标网站 ]
```

---

### 方式二:命令行 3 步部署
## ⚡ 部署指南

### 方式一:网页一键部署 (推荐)
1、Fork 本仓库

2、点击下方按钮,登录 Cloudflare 账号

[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/Mareixcode/cf-sub)

复制粘贴以下 3 行命令:
3、在 Cloudflare 中连接你 Fork 的仓库

---

### 方式二:命令行部署 (使用Wrangler CLI)

```bash
git clone https://github.com/Mareixcode/cf-cub.git
cd cf-cub && npm install
# 1. 克隆项目仓库
git clone https://github.com/Mareixcode/cf-sub.git
cd cf-sub

# 2. 安装依赖
npm install

# 3. 部署至 Cloudflare Workers
npx wrangler deploy
```

---

## ⚙️ 环境变量配置 (可选)
## ⚙️ 配置说明

### 方法 A:使用 Web UI 界面

填入机场订阅,选择目标客户端与家宽出口协议(若家宽信息已配置在环境变量中,可保持留空),点击**生成订阅链接**或**一键导入客户端**。

### 方法 B:Cloudflare 后台环境变量配置 (推荐)

部署成功后,配置您的家宽 SOCKS5 出口参数(两种)
前往 Cloudflare Dashboard -> **Workers & Pages** -> 选择您的 Worker -> **Settings** -> **Variables**,添加以下环境变量

- **方法 A (Web UI 界面直接填)**:直接在浏览器打开部署好的 Worker 网址,在网页界面上直接填入 SOCKS5 IP、端口、账号和密码。
- **方法 B (Cloudflare 环境变量)**:在 Cloudflare Dashboard -> Worker -> **Settings** -> **Variables** 中添加环境变量:
- `SOCKS_SERVER`: SOCKS5 服务器 IP/域名
- `SOCKS_PORT`: SOCKS5 端口
- `SOCKS_USERNAME`: 认证用户名
- `SOCKS_PASSWORD`: 认证密码
| 变量名 | 说明 | 示例 |
| :--- | :--- | :--- |
| `SOCKS_TYPE` | 出口协议类型 (`socks5` \| `http` \| `https` \| `ss` \| `trojan` \| `vless`) | `socks5` |
| `SOCKS_SERVER` | 家宽出口服务器 IP 或域名 | `1.2.3.4` 或 `exit.example.com` |
| `SOCKS_PORT` | 出口服务端口 | `1080` |
| `SOCKS_USERNAME` | 认证用户名 (可选) | `user` |
| `SOCKS_PASSWORD` | 认证密码 (可选) | `pass` |

---

## 🌟 核心特性
## 📡 API 路由与动态参数

- **订阅转换端点**:`GET /sub`
- `url`: 原始机场订阅 URL(需 URL 编码)
- `target`: 目标客户端 (`clash`, `singbox`, `surge`, `quanx`, `shadowrocket`)
- `socks_type`: 出口协议类型 (`socks5`, `http`, `https`, `ss`, `trojan`, `vless`)
- `socks_server`: 家宽出口 IP/域名 (可选)
- `socks_port`: 家宽出口端口 (可选)

---

## 🙏 致谢

- **[tindy2013/subconverter](https://github.com/tindy2013/subconverter)** -订阅转换工具与客户端转换
- **[Metacubex/mihomo](https://github.com/Metacubex/mihomo)** - Clash Meta 内核
- **[SagerNet/sing-box](https://github.com/SagerNet/sing-box)** - 网络代理工具
- **[Loyalsoldier/clash-rules](https://github.com/Loyalsoldier/clash-rules)** - 分流规则集
- **[ACL4SSR/ACL4SSR](https://github.com/ACL4SSR/ACL4SSR)** - 策略组与规则定义

---

## ⚠️ 免责声明 (Disclaimer)

1. **仅供学习交流**:本项目(`cf-sub`)仅供网络技术研究、开源代码学习及个人测试使用,请勿用于任何违反所在国家或地区法律法规的用途。
2. **风险自负**:使用者在部署和使用本项目时,应自行承担相关风险。开发者不对因使用、复制、修改或分发本软件而产生的任何直接或间接损失、法律纠纷、设备故障或安全后果承担任何责任。
3. **第三方服务**:本项目依赖 Cloudflare Workers 及第三方订阅节点,第三方服务的稳定性、安全性及合法性由对应服务提供方负责,本项目不做任何形式的保证或担保。
4. **合规使用**:请使用者严格遵守所在国家/地区的法律法规及网络管理规定,如因违规使用产生任何法律后果,均由使用者自行承担,与本项目开发者无关。

---

## 📬 联系方式

* ​**开发者**​: MareixHunk
* ​**Email**​: [ceohunk@gmail.com](mailto:ceohunk@gmail.com)
* ​**GitHub**​: [MareixHunk](https://github.com/Mareixcode)

---

- **Module Worker & Wrangler JSONC 架构**:采用最新标准 TypeScript Module Worker 导出与 `wrangler.jsonc` 配置文件。
- **SOCKS5 链式代理 (Exit Proxy)**:将机场节点作为前置,家宽 SOCKS5 作为后置 Exit 出口 (`dialer-proxy: 前置节点`),不改变原本的分流规则与策略组。
- **严格隐私安全**:代码与配置文件零硬编码隐私信息,支持环境变量与 URL 动态查询参数。
- **智能订阅格式解析**:支持自动检测解析 Base64 节点列表、Clash YAML 配置以及单行节点 URI 列表 (SS / Vmess / Trojan / Vless / Hysteria2)。
- **yantao.wiki 极客控制台**:集成科技风 Terminal 调试控制台,支持在线一键解析测试与 Clash 客户端快速导入。
- **Cloudflare Cache API**:针对 `/sub` 自动边缘缓存 300 秒,减少对机场源站的频繁请求。
## 📄 开源协议

## 📄 License
本项目采用 [MIT License](LICENSE) 协议开源。

[MIT License](LICENSE) &copy; 2026 [Mareixcode](https://github.com/Mareixcode)
感谢 LINUX DO 社区开发者对本项目的支持 [**LINUX.DO**](https://linux.do)
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cf-cub",
"name": "cf-sub",
"version": "1.0.0",
"description": "Cloudflare Workers Subscription Converter & SOCKS5 Chain Proxy Exit",
"description": "Cloudflare Workers Subscription Converter & SOCKS5 Chain Proxy",
"main": "src/index.ts",
"type": "module",
"scripts": {
Expand Down
73 changes: 73 additions & 0 deletions src/output/base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ClashConfig, ClashProxy } from '../types';

export function formatBase64Sub(config: ClashConfig): string {
const proxies = config.proxies || [];
const lines: string[] = [];

for (const proxy of proxies) {
const link = proxyToUri(proxy);
if (link) {
lines.push(link);
}
}

const rawText = lines.join('\n');
return btoa(unescape(encodeURIComponent(rawText)));
}

export function formatRawTextSub(config: ClashConfig): string {
const proxies = config.proxies || [];
const lines: string[] = [];

for (const proxy of proxies) {
const link = proxyToUri(proxy);
if (link) {
lines.push(link);
}
}

return lines.join('\n');
}

function proxyToUri(proxy: ClashProxy): string | null {
if (!proxy || !proxy.name) return null;

const name = encodeURIComponent(proxy.name);
const server = proxy.server;
const port = proxy.port;
const type = (proxy.type || '').toLowerCase();

if (type === 'socks5' || type === 'socks') {
let auth = '';
if (proxy.username && proxy.password) {
auth = `${encodeURIComponent(String(proxy.username))}:${encodeURIComponent(String(proxy.password))}@`;
}
return `socks5://${auth}${server}:${port}#${name}`;
} else if (type === 'ss') {
const userInfo = btoa(`${proxy.cipher}:${proxy.password}`);
return `ss://${userInfo}@${server}:${port}#${name}`;
} else if (type === 'trojan') {
return `trojan://${encodeURIComponent(String(proxy.password))}@${server}:${port}#${name}`;
} else if (type === 'vless') {
return `vless://${proxy.uuid}@${server}:${port}?encryption=none#${name}`;
} else if (type === 'vmess') {
const vmessObj = {
v: '2',
ps: proxy.name,
add: server,
port: String(port),
id: proxy.uuid,
aid: String(proxy.alterId || '0'),
scy: proxy.cipher || 'auto',
net: proxy.network || 'tcp',
type: 'none',
host: proxy.servername || '',
tls: proxy.tls ? 'tls' : '',
};
const jsonStr = JSON.stringify(vmessObj);
const base64Str = btoa(unescape(encodeURIComponent(jsonStr)));
return `vmess://${base64Str}`;
}

return null;
}
76 changes: 76 additions & 0 deletions src/output/quanx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { ClashConfig, ClashProxy } from '../types';

export function formatQuanxConfig(config: ClashConfig): string {
const proxies = config.proxies || [];
const groups = config['proxy-groups'] || [];

const nodeLines: string[] = [];
for (const proxy of proxies) {
const line = transformProxyToQuanx(proxy);
if (line) {
nodeLines.push(line);
}
}

const policyLines: string[] = [];
for (const group of groups) {
const members = (group.proxies || []).join(', ');
policyLines.push(`static=${group.name}, ${members}, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Proxy.png`);
}

return `[general]
profile_img_url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Server.png

[server_local]
${nodeLines.join('\n')}

[policy]
${policyLines.join('\n')}

[filter_local]
FINAL, ⚡ 最终出口
`;
}

function transformProxyToQuanx(proxy: ClashProxy): string | null {
if (!proxy || !proxy.name) return null;

const name = proxy.name;
const server = proxy.server;
const port = proxy.port;
const type = (proxy.type || '').toLowerCase();

let line = '';
if (type === 'socks5' || type === 'socks') {
const auth = (proxy.username && proxy.password)
? `, fast-open=false, udp-relay=true, user=${proxy.username}, password=${proxy.password}`
: ', fast-open=false, udp-relay=true';
line = `socks5=${server}:${port}${auth}, tag=${name}`;
} else if (type === 'http' || type === 'https') {
const tls = type === 'https' ? ', over-tls=true' : '';
const auth = (proxy.username && proxy.password)
? `, user=${proxy.username}, password=${proxy.password}`
: '';
line = `http=${server}:${port}${auth}${tls}, tag=${name}`;
} else if (type === 'ss') {
// QX Shadowsocks 格式
const cipher = proxy.cipher || 'chacha20-ietf-poly1305';
line = `shadowsocks=${server}:${port}, method=${cipher}, password=${proxy.password}, fast-open=false, udp-relay=true, tag=${name}`;
} else if (type === 'trojan') {
line = `trojan=${server}:${port}, password=${proxy.password}, over-tls=true, tls-verification=true, tag=${name}`;
} else if (type === 'vmess') {
// QX VMess: method 使用节点实际加密方式而非固定 none
const cipher = (proxy.cipher && proxy.cipher !== 'auto') ? String(proxy.cipher) : 'chacha20-ietf-poly1305';
const tls = proxy.tls ? ', obfs=over-tls' : '';
line = `vmess=${server}:${port}, method=${cipher}, password=${proxy.uuid}${tls}, tag=${name}`;
} else if (type === 'vless') {
// QX VLESS (需要 QX >= 1.0.30)
line = `vless=${server}:${port}, password=${proxy.uuid}, over-tls=true, tag=${name}`;
} else {
// 不支持的类型跳过
return null;
}

return line;
}

Loading
Loading