From 7851c6b84ec714efcbc8ceb8be9013d9973fa076 Mon Sep 17 00:00:00 2001 From: violettools Date: Fri, 23 Jan 2026 10:56:05 +0800 Subject: [PATCH] v1.8.5: page config mode --- README.md | 26 +-- cfspider/vless_client.py | 44 ++--- package-lock.json | 130 +++++++++++++ package.json | 5 + workers.js | 393 +++++++++++++++++++++++++++++++-------- 5 files changed, 486 insertions(+), 112 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/README.md b/README.md index 214ac7a..3799e8a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cfspider/vless_client.py b/cfspider/vless_client.py index b2aa5a1..bf08802 100644 --- a/cfspider/vless_client.py +++ b/cfspider/vless_client.py @@ -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) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6b34dfe --- /dev/null +++ b/package-lock.json @@ -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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0fd965c --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@neondatabase/serverless": "^1.0.2" + } +} diff --git a/workers.js b/workers.js index 8489ec7..da6939a 100644 --- a/workers.js +++ b/workers.js @@ -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 ` @@ -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; } } @@ -2799,79 +2834,83 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr

${t.subtitle}

- - ${userID ? ` +
VLESS PROXY -
+ ${hasEnvUUID ? '环境变量模式' : '页面配置模式'} +
${t.online}
${t.newIp}: ON
-
+
- ${isDefaultUUID ? ` -
- ⚠️ -
-
Security Notice
-
${t.defaultUuidWarning}
-
Dashboard → Workers → Settings → Variables → Add "UUID"
+ ${hasEnvUUID ? ` + +
+
+
Connection
+
${t.vlessHost}${vlessHost}
+
${t.vlessPort}443
+
${t.transport}WebSocket
+
${t.security}TLS
+
+
+
Authentication
+
${t.vlessUUID}${userID}
+
Path/${userID.substring(0,8)}...
+
${t.encryption}none
+
ModePrivate (ENV)
+
+ + ` : ` + +
+
+ 您的 UUID +
+ + +
+
+
+ UUID + 点击查看生成的 UUID +
+
+ VLESS 链接 + 请先查看 UUID +
+
+ 💡 UUID 仅在弹窗中显示一次,请务必复制保存。删除后可重新生成。 +
- ` : ''}
Connection
-
- ${t.vlessHost} - ${vlessHost} -
-
- ${t.vlessPort} - 443 -
-
- ${t.transport} - WebSocket -
-
- ${t.security} - TLS -
+
${t.vlessHost}${vlessHost}
+
${t.vlessPort}443
+
${t.transport}WebSocket
+
${t.security}TLS
-
Authentication
-
- ${t.vlessUUID} - ${userID} +
Settings
+
${t.encryption}none
+
ModePage Config
+
StoragelocalStorage
+
Status${t.online}
-
- Path - /${userID.substring(0,8)}... -
-
- ${t.encryption} - none
-
- Status - ${isDefaultUUID ? 'Public' : 'Private'} -
-
-
+ `} - -
${t.v2rayClients}
@@ -2883,26 +2922,32 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr NekoRay Surge Quantumult X +
-
-
- ` : ''} + - ${userID ? `
- 🔗 ${t.twoProxyTitle} + ${twoProxyEnabled ? 'ENV' : '可选'}
-
- ${twoProxyEnabled ? '' : ''} + ${!twoProxyEnabled && !hasEnvUUID ? ` +
+ + +
+ ` : ` +
+ ${twoProxyEnabled ? '' : ''} ${twoProxyEnabled ? t.twoProxyEnabled : t.twoProxyDisabled}
+ `}
${twoProxyEnabled ? ` +
${t.twoProxyDesc}
@@ -2911,14 +2956,22 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr
${t.twoProxyAuth}: ${twoProxyUser ? twoProxyUser.substring(0, 8) + '***' : 'None'}
- - - ` : ''}
@@ -2969,26 +3022,43 @@ function generateCFspiderPage(request, url, visitorIP, userID, newIpEnabled = tr
-
// Python Example - pip install cfspider
+
// Python 基础用法 - pip install cfspider
import cfspider
 
-# ${lang === 'zh' ? '使用 CFspider 代理池获取不同 IP' : 'Use CFspider proxy pool for different IPs'}
-for i in range(5):
+# 基础使用
 response = cfspider.get(
     "https://httpbin.org/ip",
-        cf_proxies="https://your-workers.dev"${isDefaultUUID ? '' : `,
-        uuid="your-uuid-here"`}
+    cf_proxies="https://${vlessHost}",
+    uuid="${hasEnvUUID ? userID : '<您的UUID>'}"
 )
-    print(response.json())
+print(response.json())
 
-# ${lang === 'zh' ? '固定 IP 模式 - 保持同一出口 IP' : 'Static IP mode - keep same IP'}
+# 固定 IP 模式
 response = cfspider.get(
     "https://httpbin.org/ip",
-    cf_proxies="https://your-workers.dev",
+    cf_proxies="https://${vlessHost}",
+    uuid="${hasEnvUUID ? userID : '<您的UUID>'}",
     static_ip=True
 )
+ ${twoProxyEnabled || !hasEnvUUID ? ` + +
+
// Python 双层代理用法 - 国内无法直连海外代理时使用
+
import cfspider
+
+# 双层代理:本地 → Workers → 第二层代理 → 目标
+response = cfspider.get(
+    "https://httpbin.org/ip",
+    cf_proxies="https://${vlessHost}",
+    uuid="${hasEnvUUID ? userID : '<您的UUID>'}",
+    two_proxy="${twoProxyEnabled ? twoProxy : ''}"
+)
+print(response.json())
+
+ ` : ''} +
${t.apiTitle}
@@ -3014,9 +3084,173 @@ response = cfspider.get(
+ + + + + + `;