v1.8.5: page config mode

This commit is contained in:
violettools
2026-01-23 10:56:05 +08:00
parent 68b40c321e
commit 7851c6b84e
5 changed files with 486 additions and 112 deletions

View File

@@ -39,16 +39,18 @@
vless://你的UUID@your-workers.dev:443?encryption=none&security=tls&type=ws&host=your-workers.dev&path=%2F你的UUID#CFspider
```
## v1.8.4 新特性
## v1.8.5 新特性
| 特性 | 说明 |
|------|------|
| **VLESS 协议** | 使用 VLESS 协议代理,完全隐藏 CF-Ray、CF-Worker 等 Cloudflare 头 |
| **v2ray 支持** | 支持 v2rayN/v2rayNG/Clash 等客户端直接使用 |
| **动态 IP 池** | 每次请求自动获取新的出口 IP从 300+ 全球节点选择 |
| **固定 IP 模式** | 新增 `static_ip` 参数,支持保持同一 IP 进行多次请求 |
| **双层代理** | 新增 `two_proxy` 参数,解决国内无法直连海外代理的问题,支持自定义出口 IP |
| **UUID 安全** | 支持自定义 UUID配置后需手动填写默认 UUID 界面显示警告 |
| **固定 IP 模式** | `static_ip` 参数,支持保持同一 IP 进行多次请求 |
| **双层代理** | `two_proxy` 参数,解决国内无法直连海外代理的问题,支持自定义出口 IP |
| **页面配置模式** | 无需设置环境变量,访问页面即可自动生成 UUID 并配置双层代理 |
| **环境变量模式** | 通过 Cloudflare Dashboard 设置 UUID 和 TWO_PROXY 环境变量 |
| **UUID 仅显示一次** | 页面配置模式下UUID 仅在弹窗中显示一次,关闭后加密显示 |
| **简化 API** | 只需填写 Workers 地址,自动获取配置 |
## 双层代理(国内无法直连代理时使用)
@@ -170,7 +172,7 @@ us.cliproxy.io:3010:2e75108689-region-JP:password123
## 核心优势VLESS 动态 IP 池
> **CFspider v1.8.4 采用 VLESS 协议**,每次请求自动获取新的出口 IP自动从 300+ 全球节点中选择最优节点。**完全隐藏 Cloudflare 特征**(无 CF-Ray、CF-Worker、Cf-Connecting-Ip 等头),实现真正的匿名代理。
> **CFspider v1.8.5 采用 VLESS 协议**,每次请求自动获取新的出口 IP自动从 300+ 全球节点中选择最优节点。**完全隐藏 Cloudflare 特征**(无 CF-Ray、CF-Worker、Cf-Connecting-Ip 等头),实现真正的匿名代理。
### 动态 IP 池的优势
@@ -459,7 +461,7 @@ Cloudflare Workers 免费版每日 100,000 请求,无需信用卡,无需付
## 技术架构
### VLESS 代理架构 (v1.8.3)
### VLESS 代理架构 (v1.8.5)
```
+------------------+ +------------------+
@@ -495,7 +497,7 @@ Cloudflare Workers 免费版每日 100,000 请求,无需信用卡,无需付
**VLESS vs HTTP 代理对比:**
| 对比项 | 旧方案 (HTTP 代理) | 新方案 (VLESS v1.8.3) |
| 对比项 | 旧方案 (HTTP 代理) | 新方案 (VLESS v1.8.5) |
|--------|-------------------|------------------------|
| CF 特征暴露 | 暴露 CF-Ray, CF-Worker | 完全隐藏 |
| IPv6 问题 | IPv6 固定不变 | 每次请求新 IP |
@@ -504,7 +506,7 @@ Cloudflare Workers 免费版每日 100,000 请求,无需信用卡,无需付
## 特性
### 核心特性 (v1.8.3)
### 核心特性 (v1.8.5)
- **VLESS 协议代理**通过 VLESS 协议连接 Workers完全隐藏 Cloudflare 特征
- **动态 IP **每次请求自动获取新的出口 IP 300+ 全球节点自动选择
@@ -1889,7 +1891,7 @@ except Exception as e:
"host": "your-workers.dev",
"vless_path": "/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"new_ip": true,
"version": "1.8.3",
"version": "1.8.5",
"is_default_uuid": true,
"uuid": "cfspider-public" // 仅默认 UUID 时返回
}
@@ -1915,7 +1917,7 @@ Cloudflare IP 被数百万网站使用,信誉极高。但如果对单一网站
Cloudflare CDN IP (如 172.64.x.x) 是 Anycast IP仅用于边缘加速不提供 HTTP 代理服务。必须通过 Workers 才能实现代理功能。
### 浏览器模式如何获得 CF IP
v1.8.3 已内置 VLESS 协议支持。只需填写 Workers 地址即可CFspider 会自动通过 VLESS 协议将浏览器流量从 Cloudflare IP 出口。
v1.8.5 已内置 VLESS 协议支持。只需填写 Workers 地址即可CFspider 会自动通过 VLESS 协议将浏览器流量从 Cloudflare IP 出口。
```python
browser = cfspider.Browser(cf_proxies="https://your-workers.dev")
@@ -1928,7 +1930,7 @@ browser = cfspider.Browser(cf_proxies="https://your-workers.dev")
3. 超时限制:免费版 30 秒,付费版无限制
4. VLESS 协议使用 WebSocket 传输,已内置支持
5. 浏览器模式需要额外安装 `playwright` 和 Chromium
6. **v1.8.3**VLESS 功能已集成到 `workers.js`,无需单独部署 edgetunnel
6. **v1.8.5**VLESS 功能已集成到 `workers.js`,无需单独部署 edgetunnel
7. 配置自定义 UUID 后Python 库必须填写 `uuid` 参数
## 致谢
@@ -1937,7 +1939,7 @@ browser = cfspider.Browser(cf_proxies="https://your-workers.dev")
edgetunnel 是一个优秀的 Cloudflare Workers VLESS 代理实现,感谢 [@cmliu](https://github.com/cmliu) 的开源贡献。
**v1.8.3 说明:** VLESS 功能已完全集成到 `workers.js` 中,无需单独部署 edgetunnel。只需部署本项目的 `workers.js` 即可同时获得 HTTP 代理接口和 VLESS 协议支持。
**v1.8.5 说明:** VLESS 功能已完全集成到 `workers.js` 中,无需单独部署 edgetunnel。只需部署本项目的 `workers.js` 即可同时获得 HTTP 代理接口和 VLESS 协议支持。支持两种配置方式:环境变量模式和页面配置模式。
- edgetunnel 仓库https://github.com/cmliu/edgetunnel

View File

@@ -494,28 +494,28 @@ class LocalVlessProxy:
else:
# 直接连接目标
conn = vless.connect(host, port)
# 重建请求
lines = original_request.split(b'\r\n')
lines[0] = f'{method} {path} HTTP/1.1'.encode('utf-8')
# 更新 Host 头
new_lines = [lines[0]]
has_host = False
for line in lines[1:]:
if line.lower().startswith(b'host:'):
new_lines.append(f'Host: {host}'.encode('utf-8'))
has_host = True
elif line.lower().startswith(b'proxy-'):
continue
else:
new_lines.append(line)
if not has_host:
new_lines.insert(1, f'Host: {host}'.encode('utf-8'))
request = b'\r\n'.join(new_lines)
conn.send(request)
# 重建请求
lines = original_request.split(b'\r\n')
lines[0] = f'{method} {path} HTTP/1.1'.encode('utf-8')
# 更新 Host 头
new_lines = [lines[0]]
has_host = False
for line in lines[1:]:
if line.lower().startswith(b'host:'):
new_lines.append(f'Host: {host}'.encode('utf-8'))
has_host = True
elif line.lower().startswith(b'proxy-'):
continue
else:
new_lines.append(line)
if not has_host:
new_lines.insert(1, f'Host: {host}'.encode('utf-8'))
request = b'\r\n'.join(new_lines)
conn.send(request)
# 读取响应并转发
self._relay_response(client, conn)

130
package-lock.json generated Normal file
View File

@@ -0,0 +1,130 @@
{
"name": "CFspider",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@neondatabase/serverless": "^1.0.2"
}
},
"node_modules/@neondatabase/serverless": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/@neondatabase/serverless/-/serverless-1.0.2.tgz",
"integrity": "sha512-I5sbpSIAHiB+b6UttofhrN/UJXII+4tZPAq1qugzwCwLIL8EZLV7F/JyHUrEIiGgQpEXzpnjlJ+zwcEhheGvCw==",
"license": "MIT",
"dependencies": {
"@types/node": "^22.15.30",
"@types/pg": "^8.8.0"
},
"engines": {
"node": ">=19.0.0"
}
},
"node_modules/@types/node": {
"version": "22.19.7",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.7.tgz",
"integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/pg": {
"version": "8.16.0",
"resolved": "https://registry.npmmirror.com/@types/pg/-/pg-8.16.0.tgz",
"integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"pg-protocol": "*",
"pg-types": "^2.2.0"
}
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"license": "ISC",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-protocol": {
"version": "1.11.0",
"resolved": "https://registry.npmmirror.com/pg-protocol/-/pg-protocol-1.11.0.tgz",
"integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==",
"license": "MIT"
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"license": "MIT",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
}
}
}

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"@neondatabase/serverless": "^1.0.2"
}
}

View File

@@ -2301,7 +2301,10 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr
const longitude = request.cf?.longitude || '0';
const continent = request.cf?.continent || 'XX';
const lang = url.searchParams.get('lang') || 'zh';
const VERSION = '1.8.4';
const VERSION = '1.8.5';
// 是否使用环境变量配置 UUID
const hasEnvUUID = !isDefaultUUID;
// 解析双层代理配置
let twoProxyHost = '', twoProxyPort = '', twoProxyUser = '', twoProxyPass = '', twoProxyEnabled = false;
@@ -2409,6 +2412,9 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr
const continentName = continentNames[continent] || continent;
const cityName = city;
// 页面配置功能
const pageConfigEnabled = !hasEnvUUID; // 没有环境变量配置时启用页面配置
return `<!DOCTYPE html>
<html lang="${lang === 'zh' ? 'zh-CN' : 'en'}">
<head>
@@ -2784,10 +2790,39 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr
}
.footer-credits a { color: var(--accent-cyan); text-decoration: none; }
/* Modal Styles */
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); z-index: 1000; justify-content: center; align-items: center; }
.modal.show { display: flex; }
.modal-content { background: var(--bg-secondary); border: 2px solid var(--accent-cyan); border-radius: 16px; padding: 32px; max-width: 600px; width: 90%; max-height: 90vh; overflow-y: auto; }
.modal-title { font-family: 'Orbitron', sans-serif; color: var(--accent-cyan); font-size: 1.3rem; margin-bottom: 16px; }
.modal-subtitle { color: var(--text-secondary); margin-bottom: 20px; font-size: 0.9rem; }
.modal-uuid { background: var(--bg-primary); border: 1px solid var(--border-color); padding: 16px; border-radius: 8px; font-family: monospace; font-size: 1rem; color: var(--accent-magenta); text-align: center; margin-bottom: 16px; word-break: break-all; user-select: all; }
.modal-vless { background: var(--bg-primary); border: 1px solid var(--border-color); padding: 12px; border-radius: 8px; font-family: monospace; font-size: 0.75rem; color: var(--accent-cyan); word-break: break-all; margin-bottom: 16px; max-height: 100px; overflow-y: auto; }
.modal-warning { color: var(--accent-orange); font-size: 0.85rem; margin: 16px 0; padding: 12px; background: rgba(219,109,40,0.15); border-radius: 8px; }
.modal-buttons { display: flex; gap: 12px; flex-wrap: wrap; }
.modal-btn { flex: 1; min-width: 120px; padding: 12px 20px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-family: inherit; transition: all 0.3s; }
.modal-btn-primary { background: var(--accent-cyan); color: var(--bg-primary); }
.modal-btn-primary:hover { background: var(--accent-green); }
.modal-btn-secondary { background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border-color); }
.modal-input { width: 100%; padding: 12px 16px; background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 8px; color: var(--text-primary); font-size: 1rem; font-family: inherit; margin-bottom: 12px; }
.modal-input:focus { outline: none; border-color: var(--accent-cyan); }
.modal-hint { font-size: 0.8rem; color: var(--text-secondary); margin-bottom: 16px; }
/* Page Config Styles */
.config-mode-badge { background: var(--accent-green); color: #fff; padding: 4px 12px; border-radius: 20px; font-size: 0.7rem; font-weight: 600; }
.config-mode-badge.env { background: var(--accent-cyan); }
.config-mode-badge.page { background: var(--accent-magenta); }
.uuid-masked { color: var(--text-secondary); letter-spacing: 2px; }
.proxy-masked { color: var(--text-secondary); }
.action-btn { padding: 6px 14px; border-radius: 6px; border: 1px solid var(--border-color); background: var(--bg-tertiary); color: var(--text-primary); cursor: pointer; font-size: 0.8rem; transition: all 0.2s; }
.action-btn:hover { border-color: var(--accent-cyan); color: var(--accent-cyan); }
.action-btn.danger:hover { border-color: var(--accent-orange); color: var(--accent-orange); }
@media (max-width: 768px) {
.logo { font-size: 2.5rem; }
.vless-header { flex-direction: column; align-items: flex-start; }
.vless-config-grid { grid-template-columns: 1fr; }
.modal-buttons { flex-direction: column; }
}
</style>
</head>
@@ -2799,79 +2834,83 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr
<p class="subtitle">${t.subtitle}</p>
</header>
<!-- VLESS Hero Section -->
${userID ? `
<!-- VLESS Hero Section with Page Config -->
<div class="vless-hero">
<div class="vless-header">
<div class="vless-title-main">
<span>VLESS PROXY</span>
</div>
<span class="config-mode-badge ${hasEnvUUID ? 'env' : 'page'}">${hasEnvUUID ? '环境变量模式' : '页面配置模式'}</span>
</div>
<div class="vless-status">
<div class="status-pill active"><span class="status-dot"></span> ${t.online}</div>
<div class="status-pill">${t.newIp}: ON</div>
</div>
</div>
</div>
${isDefaultUUID ? `
<div class="warning-banner">
<span class="warning-icon">⚠️</span>
<div class="warning-content">
<div class="warning-title">Security Notice</div>
<div class="warning-text">${t.defaultUuidWarning}</div>
<div class="warning-path">Dashboard → Workers → Settings → Variables → Add "UUID"</div>
${hasEnvUUID ? `
<!-- 环境变量模式:直接显示配置 -->
<div class="vless-config-grid">
<div class="config-card">
<div class="config-card-title">Connection</div>
<div class="config-item"><span class="config-label">${t.vlessHost}</span><span class="config-value">${vlessHost}</span></div>
<div class="config-item"><span class="config-label">${t.vlessPort}</span><span class="config-value">443</span></div>
<div class="config-item"><span class="config-label">${t.transport}</span><span class="config-value">WebSocket</span></div>
<div class="config-item"><span class="config-label">${t.security}</span><span class="config-value success">TLS</span></div>
</div>
<div class="config-card">
<div class="config-card-title">Authentication</div>
<div class="config-item"><span class="config-label">${t.vlessUUID}</span><span class="config-value uuid">${userID}</span></div>
<div class="config-item"><span class="config-label">Path</span><span class="config-value">/${userID.substring(0,8)}...</span></div>
<div class="config-item"><span class="config-label">${t.encryption}</span><span class="config-value">none</span></div>
<div class="config-item"><span class="config-label">Mode</span><span class="config-value success">Private (ENV)</span></div>
</div>
</div>
<div class="vless-link-box" onclick="copyVlessLink(this)">
<span class="copy-hint" id="copyHint">${t.vlessCopy}</span>
<div class="vless-link-label"><span>${t.vlessLink}</span></div>
<div class="vless-link-text" id="vlessLink">${vlessLink}</div>
</div>
` : `
<!-- 页面配置模式UUID 管理 -->
<div class="config-card" style="margin-bottom: 24px;">
<div class="config-card-title" style="display: flex; justify-content: space-between; align-items: center;">
<span>您的 UUID</span>
<div style="display: flex; gap: 8px;">
<button class="action-btn" onclick="showUuidModal()">查看</button>
<button class="action-btn danger" onclick="deleteUuid()">删除</button>
</div>
</div>
<div class="config-item">
<span class="config-label">UUID</span>
<span class="config-value uuid-masked" id="uuidDisplay">点击查看生成的 UUID</span>
</div>
<div class="config-item">
<span class="config-label">VLESS 链接</span>
<span class="config-value" id="vlessStatus" style="color: var(--text-secondary);">请先查看 UUID</span>
</div>
<div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border-color);">
💡 UUID 仅在弹窗中显示一次,请务必复制保存。删除后可重新生成。
</div>
</div>
` : ''}
<div class="vless-config-grid">
<div class="config-card">
<div class="config-card-title">Connection</div>
<div class="config-item">
<span class="config-label">${t.vlessHost}</span>
<span class="config-value">${vlessHost}</span>
</div>
<div class="config-item">
<span class="config-label">${t.vlessPort}</span>
<span class="config-value">443</span>
</div>
<div class="config-item">
<span class="config-label">${t.transport}</span>
<span class="config-value">WebSocket</span>
</div>
<div class="config-item">
<span class="config-label">${t.security}</span>
<span class="config-value success">TLS</span>
</div>
<div class="config-item"><span class="config-label">${t.vlessHost}</span><span class="config-value">${vlessHost}</span></div>
<div class="config-item"><span class="config-label">${t.vlessPort}</span><span class="config-value">443</span></div>
<div class="config-item"><span class="config-label">${t.transport}</span><span class="config-value">WebSocket</span></div>
<div class="config-item"><span class="config-label">${t.security}</span><span class="config-value success">TLS</span></div>
</div>
<div class="config-card">
<div class="config-card-title">Authentication</div>
<div class="config-item">
<span class="config-label">${t.vlessUUID}</span>
<span class="config-value uuid">${userID}</span>
<div class="config-card-title">Settings</div>
<div class="config-item"><span class="config-label">${t.encryption}</span><span class="config-value">none</span></div>
<div class="config-item"><span class="config-label">Mode</span><span class="config-value" style="color: var(--accent-magenta);">Page Config</span></div>
<div class="config-item"><span class="config-label">Storage</span><span class="config-value">localStorage</span></div>
<div class="config-item"><span class="config-label">Status</span><span class="config-value success">${t.online}</span></div>
</div>
<div class="config-item">
<span class="config-label">Path</span>
<span class="config-value">/${userID.substring(0,8)}...</span>
</div>
<div class="config-item">
<span class="config-label">${t.encryption}</span>
<span class="config-value">none</span>
</div>
<div class="config-item">
<span class="config-label">Status</span>
<span class="config-value success">${isDefaultUUID ? 'Public' : 'Private'}</span>
</div>
</div>
</div>
`}
<div class="vless-link-box" onclick="copyVlessLink(this)">
<span class="copy-hint" id="copyHint">${t.vlessCopy}</span>
<div class="vless-link-label">
<span>${t.vlessLink}</span>
</div>
<div class="vless-link-text" id="vlessLink">${vlessLink}</div>
</div>
<div class="clients-section">
<div class="clients-title">${t.v2rayClients}</div>
<div class="clients-grid">
@@ -2883,26 +2922,32 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr
<span class="client-tag">NekoRay</span>
<span class="client-tag">Surge</span>
<span class="client-tag">Quantumult X</span>
</div>
</div>
</div>
</div>
` : ''}
</div>
<!-- Two-Proxy Section -->
${userID ? `
<div class="two-proxy-section" style="background: ${twoProxyEnabled ? 'linear-gradient(135deg, rgba(63,185,80,0.15) 0%, rgba(88,166,255,0.15) 100%)' : 'var(--bg-secondary)'}; border: ${twoProxyEnabled ? '2px solid var(--accent-green)' : '1px solid var(--border-color)'}; border-radius: 16px; padding: 24px; margin-bottom: 32px;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; flex-wrap: wrap; gap: 12px;">
<div style="font-family: 'Orbitron', sans-serif; font-size: 1.2rem; color: ${twoProxyEnabled ? 'var(--accent-green)' : 'var(--text-secondary)'}; display: flex; align-items: center; gap: 10px;">
<span>🔗</span>
<span>${t.twoProxyTitle}</span>
<span class="config-mode-badge ${twoProxyEnabled ? 'env' : 'page'}" style="font-size: 0.65rem;">${twoProxyEnabled ? 'ENV' : '可选'}</span>
</div>
<div class="status-pill ${twoProxyEnabled ? 'active' : ''}" style="background: var(--bg-tertiary); border: 1px solid ${twoProxyEnabled ? 'var(--accent-green)' : 'var(--border-color)'}; padding: 6px 14px; border-radius: 20px; font-size: 0.8rem;">
${twoProxyEnabled ? '<span class="status-dot" style="width: 8px; height: 8px; border-radius: 50%; background: var(--accent-green); animation: pulse 2s infinite; display: inline-block; margin-right: 6px;"></span>' : ''}
${!twoProxyEnabled && !hasEnvUUID ? `
<div style="display: flex; gap: 8px;">
<button class="action-btn" onclick="showProxyModal()">配置</button>
<button class="action-btn danger" onclick="deleteProxy()">删除</button>
</div>
` : `
<div class="status-pill ${twoProxyEnabled ? 'active' : ''}">
${twoProxyEnabled ? '<span class="status-dot"></span>' : ''}
${twoProxyEnabled ? t.twoProxyEnabled : t.twoProxyDisabled}
</div>
`}
</div>
${twoProxyEnabled ? `
<!-- 环境变量模式:显示双层代理配置 -->
<div style="background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
<div style="font-size: 0.8rem; color: var(--accent-cyan); margin-bottom: 8px;">${t.twoProxyDesc}</div>
<div style="display: flex; gap: 24px; flex-wrap: wrap; font-size: 0.9rem;">
@@ -2911,14 +2956,22 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr
<div><span style="color: var(--text-secondary);">${t.twoProxyAuth}:</span> <span style="color: ${twoProxyUser ? 'var(--accent-yellow)' : 'var(--text-secondary)'};">${twoProxyUser ? twoProxyUser.substring(0, 8) + '***' : 'None'}</span></div>
</div>
</div>
<div class="vless-link-box" onclick="copyTwoProxyLink(this)" style="background: var(--bg-primary); border: 2px solid var(--accent-green); border-radius: 12px; padding: 20px; cursor: pointer; transition: all 0.3s; position: relative;">
<span class="copy-hint" id="copyHint2" style="position: absolute; top: 16px; right: 16px; font-size: 0.75rem; color: var(--text-secondary); background: var(--bg-tertiary); padding: 4px 10px; border-radius: 4px;">${t.vlessCopy}</span>
<div style="font-size: 0.75rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 12px; display: flex; align-items: center; gap: 8px;">
<span>🚀</span>
<span>${t.twoProxyLink} (${t.exitIp}: ${twoProxyHost})</span>
<div class="vless-link-box" onclick="copyTwoProxyLink(this)" style="background: var(--bg-primary); border: 2px solid var(--accent-green);">
<span class="copy-hint" id="copyHint2">${t.vlessCopy}</span>
<div class="vless-link-label"><span>${t.twoProxyLink} (${t.exitIp}: ${twoProxyHost})</span></div>
<div class="vless-link-text" id="twoProxyLinkText">${twoProxyLink}</div>
</div>
` : `
<!-- 页面配置模式或未配置 -->
${!hasEnvUUID ? `
<div style="background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 8px; padding: 16px;">
<div class="config-item">
<span class="config-label">双层代理</span>
<span class="config-value proxy-masked" id="proxyDisplay">未配置</span>
</div>
<div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border-color);">
💡 国内无法直连海外代理时使用格式host:port 或 host:port:user:pass
</div>
<div class="vless-link-text" id="twoProxyLinkText" style="font-size: 0.85rem; color: var(--accent-green); word-break: break-all; line-height: 1.8; padding: 12px; background: var(--bg-secondary); border-radius: 8px; user-select: all;">${twoProxyLink}</div>
</div>
` : `
<div style="background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 8px; padding: 24px;">
@@ -2935,8 +2988,8 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr
</div>
</div>
`}
`}
</div>
` : ''}
<!-- Stats Grid -->
<div class="stats-grid">
@@ -2969,26 +3022,43 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr
<!-- Code Example -->
<div class="code-section">
<div class="code-header">// Python Example - pip install cfspider</div>
<div class="code-header">// Python 基础用法 - pip install cfspider</div>
<pre><span class="code-keyword">import</span> cfspider
<span class="code-comment"># ${lang === 'zh' ? '使用 CFspider 代理池获取不同 IP' : 'Use CFspider proxy pool for different IPs'}</span>
<span class="code-keyword">for</span> i <span class="code-keyword">in</span> range(<span class="code-string">5</span>):
<span class="code-comment"># 基础使用</span>
response = cfspider.<span class="code-function">get</span>(
<span class="code-string">"https://httpbin.org/ip"</span>,
cf_proxies=<span class="code-string">"https://your-workers.dev"</span>${isDefaultUUID ? '' : `,
uuid=<span class="code-string">"your-uuid-here"</span>`}
cf_proxies=<span class="code-string">"https://${vlessHost}"</span>,
uuid=<span class="code-string">"${hasEnvUUID ? userID : '<您的UUID>'}"</span>
)
<span class="code-function">print</span>(response.json())
<span class="code-function">print</span>(response.json())
<span class="code-comment"># ${lang === 'zh' ? '固定 IP 模式 - 保持同一出口 IP' : 'Static IP mode - keep same IP'}</span>
<span class="code-comment"># 固定 IP 模式</span>
response = cfspider.<span class="code-function">get</span>(
<span class="code-string">"https://httpbin.org/ip"</span>,
cf_proxies=<span class="code-string">"https://your-workers.dev"</span>,
cf_proxies=<span class="code-string">"https://${vlessHost}"</span>,
uuid=<span class="code-string">"${hasEnvUUID ? userID : '<您的UUID>'}"</span>,
static_ip=<span class="code-keyword">True</span>
)</pre>
</div>
${twoProxyEnabled || !hasEnvUUID ? `
<!-- Two-Proxy Code Example -->
<div class="code-section">
<div class="code-header">// Python 双层代理用法 - 国内无法直连海外代理时使用</div>
<pre><span class="code-keyword">import</span> cfspider
<span class="code-comment"># 双层代理:本地 → Workers → 第二层代理 → 目标</span>
response = cfspider.<span class="code-function">get</span>(
<span class="code-string">"https://httpbin.org/ip"</span>,
cf_proxies=<span class="code-string">"https://${vlessHost}"</span>,
uuid=<span class="code-string">"${hasEnvUUID ? userID : '<您的UUID>'}"</span>,
two_proxy=<span class="code-string">"${twoProxyEnabled ? twoProxy : '<host:port:user:pass>'}"</span>
)
<span class="code-function">print</span>(response.json())</pre>
</div>
` : ''}
<!-- API Section -->
<div class="api-section">
<div class="section-title">${t.apiTitle}</div>
@@ -3014,9 +3084,173 @@ response = cfspider.<span class="code-function">get</span>(
</footer>
</div>
<!-- UUID Modal -->
<div class="modal" id="uuidModal">
<div class="modal-content">
<div class="modal-title">您的 UUID</div>
<div class="modal-subtitle">请立即复制保存,关闭后将无法再次查看明文</div>
<div class="modal-uuid" id="uuidShowInModal"></div>
<button class="modal-btn modal-btn-primary" style="width: 100%; margin-bottom: 16px;" onclick="copyUuid()">复制 UUID</button>
<div class="modal-title" style="font-size: 1rem; margin-top: 16px;">VLESS 导入链接</div>
<div class="modal-vless" id="vlessShowInModal"></div>
<button class="modal-btn modal-btn-primary" style="width: 100%; margin-bottom: 16px;" onclick="copyVlessFromModal()">复制 VLESS 链接</button>
<div class="modal-warning">⚠️ 以上信息仅显示一次,关闭后无法再次查看明文</div>
<div class="modal-buttons">
<button class="modal-btn modal-btn-secondary" onclick="closeUuidModal()">我已保存,关闭</button>
</div>
</div>
</div>
<!-- Proxy Modal -->
<div class="modal" id="proxyModal">
<div class="modal-content">
<div class="modal-title">配置双层代理</div>
<div class="modal-subtitle">用于国内无法直连海外代理 IP 时使用</div>
<input type="text" class="modal-input" id="proxyInput" placeholder="格式: host:port 或 host:port:user:pass">
<div class="modal-hint">示例: us.cliproxy.io:3010:username:password</div>
<div class="modal-buttons">
<button class="modal-btn modal-btn-secondary" onclick="closeProxyModal()">取消</button>
<button class="modal-btn modal-btn-primary" onclick="saveProxy()">确认</button>
</div>
</div>
</div>
<script>
const HOST = '${vlessHost}';
const ENV_UUID = ${hasEnvUUID ? `'${userID}'` : 'null'};
const ENV_TWO_PROXY = ${twoProxyEnabled ? `'${twoProxy}'` : 'null'};
const STORAGE_KEY = 'cfspider_config_' + HOST;
// 配置管理
function loadConfig() {
if (ENV_UUID) return { uuid: ENV_UUID, uuidViewed: true, twoProxy: ENV_TWO_PROXY || '' };
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) return JSON.parse(saved);
const config = { uuid: crypto.randomUUID(), uuidViewed: false, twoProxy: '' };
saveConfig(config);
return config;
}
function saveConfig(config) {
if (!ENV_UUID) localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
}
let config = loadConfig();
function maskUuid(uuid) {
return uuid.substring(0, 4) + '****-****-****-****-' + uuid.slice(-4);
}
function maskProxy(proxy) {
if (!proxy) return '未配置';
const parts = proxy.split(':');
if (parts.length >= 2) {
return parts[0].substring(0, 4) + '****:' + parts[1] + (parts.length > 2 ? ':****' : '');
}
return '****';
}
function updateDisplay() {
if (ENV_UUID) return; // 环境变量模式不需要更新
const uuidDisplay = document.getElementById('uuidDisplay');
const vlessStatus = document.getElementById('vlessStatus');
const proxyDisplay = document.getElementById('proxyDisplay');
if (uuidDisplay) {
if (config.uuidViewed) {
uuidDisplay.textContent = maskUuid(config.uuid);
uuidDisplay.classList.add('uuid-masked');
} else {
uuidDisplay.textContent = '点击查看生成的 UUID';
uuidDisplay.classList.remove('uuid-masked');
}
}
if (vlessStatus) {
vlessStatus.textContent = config.uuidViewed ? '已查看,请在弹窗中复制' : '请先查看 UUID';
}
if (proxyDisplay) {
proxyDisplay.textContent = maskProxy(config.twoProxy);
}
updateCodeExamples();
}
function updateCodeExamples() {
// 更新代码示例中的 UUID 占位符
const codeSection = document.querySelector('.code-section pre');
if (!codeSection || ENV_UUID) return;
const placeholder = config.uuidViewed ? '<已查看请粘贴您的UUID>' : '<请先查看UUID>';
// 代码示例在服务端渲染,这里可以添加动态更新逻辑
}
// UUID 操作
function showUuidModal() {
document.getElementById('uuidShowInModal').textContent = config.uuid;
const vlessLink = 'vless://' + config.uuid + '@' + HOST + ':443?security=tls&type=ws&host=' + HOST + '&sni=' + HOST + '&path=%2F' + config.uuid + '&encryption=none#CFspider';
document.getElementById('vlessShowInModal').textContent = vlessLink;
document.getElementById('uuidModal').classList.add('show');
}
function copyUuid() {
navigator.clipboard.writeText(config.uuid);
alert('UUID 已复制到剪贴板');
}
function copyVlessFromModal() {
const vlessLink = 'vless://' + config.uuid + '@' + HOST + ':443?security=tls&type=ws&host=' + HOST + '&sni=' + HOST + '&path=%2F' + config.uuid + '&encryption=none#CFspider';
navigator.clipboard.writeText(vlessLink);
alert('VLESS 链接已复制到剪贴板');
}
function closeUuidModal() {
config.uuidViewed = true;
saveConfig(config);
document.getElementById('uuidModal').classList.remove('show');
updateDisplay();
}
function deleteUuid() {
if (!confirm('确定删除当前 UUID将生成一个新的 UUID。')) return;
config.uuid = crypto.randomUUID();
config.uuidViewed = false;
saveConfig(config);
updateDisplay();
alert('已生成新的 UUID请点击查看并保存');
}
// 双层代理操作
function showProxyModal() {
document.getElementById('proxyInput').value = config.twoProxy;
document.getElementById('proxyModal').classList.add('show');
}
function closeProxyModal() {
document.getElementById('proxyModal').classList.remove('show');
}
function saveProxy() {
const value = document.getElementById('proxyInput').value.trim();
config.twoProxy = value;
saveConfig(config);
closeProxyModal();
updateDisplay();
}
function deleteProxy() {
if (!confirm('确定删除双层代理配置?')) return;
config.twoProxy = '';
saveConfig(config);
updateDisplay();
}
// 复制功能(环境变量模式)
function copyVlessLink(el) {
const link = document.getElementById('vlessLink').innerText;
const link = document.getElementById('vlessLink')?.innerText;
if (!link) return;
const hint = document.getElementById('copyHint');
navigator.clipboard.writeText(link).then(() => {
hint.textContent = '${t.copySuccess}';
@@ -3044,6 +3278,9 @@ response = cfspider.<span class="code-function">get</span>(
}, 2000);
});
}
// 初始化
updateDisplay();
</script>
</body>
</html>`;