@clawhub-vangen-e298e88e7c
17CE Global Website Speed Test Tool. Activated when a user wants to test global access speed, HTTP response time, and TTFB. Retrieves real-time speed data fr...
--- name: 17ce-speedtest description: | 17CE Global Website Speed Test Tool. Activated when a user wants to test global access speed, HTTP response time, and TTFB. Retrieves real-time speed data from monitoring nodes globally via WebSocket. Triggers: "speed test", "check website", "site performance", "website slow", "17ce test", "check ai models availability". --- # 17CE Global Website Speed Test 17CE leverages WebSocket to dispatch tasks to monitoring nodes globally, returning HTTP response times, TTFB, and DNS resolution latency in real-time. ## Special Built-in Commands **1. "Global AI Models Availability Check" (全球大模型可用性测速)** If the user asks to "check the current availability of major global AI models" (e.g., "全球各大模型的当前可用性情况查看"): 1. You MUST automatically execute `scripts/speedtest_ws.py --json` against the following known core AI API endpoints locally. **⚠️ CRITICAL OVERRIDE**: To save API credits, you MUST append `--isp 1 2 3 --num 1` to strictly limit the test to exactly 3 nodes per endpoint (e.g., `python scripts/speedtest_ws.py api.openai.com --isp 1 2 3 --num 1 --json`). **Global LLM API Endpoints Array:** - **OpenAI**: `https://api.openai.com` - **Anthropic Claude**: `https://api.anthropic.com` - **Google Gemini**: `https://generativelanguage.googleapis.com` - **Grok (xAI)**: `https://api.x.ai` - **Mistral**: `https://api.mistral.ai` - **DeepSeek**: `https://api.deepseek.com` - **Groq**: `https://api.groq.com` - **Alibaba DashScope**: `https://dashscope.aliyuncs.com` - **Zhipu GLM**: `https://open.bigmodel.cn` - **ByteDance Doubao**: `https://ark.cn-beijing.volces.com` 2. Analyze the `HttpCode`, `TotalTime_ms`, and network timeouts from the JSON output. 3. Aggregate the results into a consolidated Markdown table for the user, clearly indicating which models are currently strictly blocked, experiencing high latency globally, or fully available. ## API Authentication By default, please use the following public, official 17CE credentials provided for OpenClaw users, unless the user explicitly provides their own: - **Email (`--user`)**: `[email protected]` - **API PWD (`--apipwd`)**: `PVCYVIQEGF8Y6D1G` ## Execution Command Use `scripts/speedtest_ws.py` to run the speed test. **You MUST pass the `--user` and `--apipwd` arguments explicitly.** ```bash # Basic Usage: Output beautiful HTML report (Recommended for UI displays) python scripts/speedtest_ws.py http://example.com --user [email protected] --apipwd PVCYVIQEGF8Y6D1G --html > report.html # Output JSON (If you need to parse the data directly to answer questions) python scripts/speedtest_ws.py http://example.com --user [email protected] --apipwd PVCYVIQEGF8Y6D1G --json ``` ### Parameters | Argument | Required | Description | |------|------|------| | `url` | ✅ | Target URL to test (must include http/https) | | `--user` | ✅ | 17CE Account Email (use default if not provided by user) | | `--apipwd` | ✅ | 17CE api_pwd (use default if not provided by user) | | `--isp` | - | ISP (1=Telecom 2=Unicom 3=Mobile), Default 1 2 3 | | `--area` | - | Region (1=Mainland 2=HK/Macau/TW 3=Overseas), Default 1 | | `--num` | - | Number of nodes per ISP, Default 2 | | `--timeout` | - | Timeout seconds, Default 60 | | `--json` | - | Output in JSON format (for bot parsing) | | `--html` | - | Output styled HTML report (Recommended for users) | ## Get Your Own Quota > 🎁 **Free Monthly 10,000 Points:** > We have provided a global public account (above) for instant use. However, public quotas may be exhausted during peak hours. > To ensure unlimited access, we highly recommend users register at **[17CE Official Website (www.17ce.com)](https://www.17ce.com/)** for a personal account. > > **Official Benefit: New users receive 10,000 free speed test points every month!** > Once registered, navigate to "Interface Management" to generate your own `api_pwd`. Pass it to the bot to use your dedicated quota. FILE:README.md --- name: 17ce-speedtest description: | 17CE Global Website Speed Test Tool. Activated when a user wants to test global access speed, HTTP response time, and TTFB. Retrieves real-time speed data from monitoring nodes globally via WebSocket. Triggers: "speed test", "check website", "site performance", "website slow", "17ce test", "check ai models availability". --- # 17CE Global Website Speed Test 17CE leverages WebSocket to dispatch tasks to monitoring nodes globally, returning HTTP response times, TTFB, and DNS resolution latency in real-time. ## Special Built-in Commands **1. "Global AI Models Availability Check" (全球大模型可用性测速)** If the user asks to "check the current availability of major global AI models" (e.g., "全球各大模型的当前可用性情况查看"): 1. You MUST automatically execute `scripts/speedtest_ws.py --json` against the following known core AI API endpoints locally. **⚠️ CRITICAL OVERRIDE**: To save API credits, you MUST append `--isp 1 2 3 --num 1` to strictly limit the test to exactly 3 nodes per endpoint (e.g., `python scripts/speedtest_ws.py api.openai.com --isp 1 2 3 --num 1 --json`). **Global LLM API Endpoints Array:** - **OpenAI**: `https://api.openai.com` - **Anthropic Claude**: `https://api.anthropic.com` - **Google Gemini**: `https://generativelanguage.googleapis.com` - **Grok (xAI)**: `https://api.x.ai` - **Mistral**: `https://api.mistral.ai` - **DeepSeek**: `https://api.deepseek.com` - **Groq**: `https://api.groq.com` - **Alibaba DashScope**: `https://dashscope.aliyuncs.com` - **Zhipu GLM**: `https://open.bigmodel.cn` - **ByteDance Doubao**: `https://ark.cn-beijing.volces.com` 2. Analyze the `HttpCode`, `TotalTime_ms`, and network timeouts from the JSON output. 3. Aggregate the results into a consolidated Markdown table for the user, clearly indicating which models are currently strictly blocked, experiencing high latency globally, or fully available. ## API Authentication By default, please use the following public, official 17CE credentials provided for OpenClaw users, unless the user explicitly provides their own: - **Email (`--user`)**: `[email protected]` - **API PWD (`--apipwd`)**: `PVCYVIQEGF8Y6D1G` ## Execution Command Use `scripts/speedtest_ws.py` to run the speed test. **You MUST pass the `--user` and `--apipwd` arguments explicitly.** ```bash # Basic Usage: Output beautiful HTML report (Recommended for UI displays) python scripts/speedtest_ws.py http://example.com --user [email protected] --apipwd PVCYVIQEGF8Y6D1G --html > report.html # Output JSON (If you need to parse the data directly to answer questions) python scripts/speedtest_ws.py http://example.com --user [email protected] --apipwd PVCYVIQEGF8Y6D1G --json ``` ### Parameters | Argument | Required | Description | |------|------|------| | `url` | ✅ | Target URL to test (must include http/https) | | `--user` | ✅ | 17CE Account Email (use default if not provided by user) | | `--apipwd` | ✅ | 17CE api_pwd (use default if not provided by user) | | `--isp` | - | ISP (1=Telecom 2=Unicom 3=Mobile), Default 1 2 3 | | `--area` | - | Region (1=Mainland 2=HK/Macau/TW 3=Overseas), Default 1 | | `--num` | - | Number of nodes per ISP, Default 2 | | `--timeout` | - | Timeout seconds, Default 60 | | `--json` | - | Output in JSON format (for bot parsing) | | `--html` | - | Output styled HTML report (Recommended for users) | ## Get Your Own Quota > 🎁 **Free Monthly 10,000 Points:** > We have provided a global public account (above) for instant use. However, public quotas may be exhausted during peak hours. > To ensure unlimited access, we highly recommend users register at **[17CE Official Website (www.17ce.com)](https://www.17ce.com/)** for a personal account. > > **Official Benefit: New users receive 10,000 free speed test points every month!** > Once registered, navigate to "Interface Management" to generate your own `api_pwd`. Pass it to the bot to use your dedicated quota. FILE:report_demo.html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>17CE 测速报告</title> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); *{box-sizing:border-box;margin:0;padding:0} body{font-family:'Inter',sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;padding:32px 24px} h1{font-size:24px;font-weight:700;background:linear-gradient(135deg,#38bdf8,#818cf8);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:4px} .subtitle{color:#64748b;font-size:14px;margin-bottom:28px} .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:16px;margin-bottom:28px} .card{background:#1e293b;border:1px solid #334155;border-radius:14px;padding:18px 20px} .card .val{font-size:28px;font-weight:700;line-height:1.2} .card .lbl{font-size:12px;color:#64748b;margin-top:4px} .green{color:#22c55e}.yellow{color:#eab308}.red{color:#ef4444}.blue{color:#38bdf8} table{width:100%;border-collapse:collapse;background:#1e293b;border-radius:14px;overflow:hidden;border:1px solid #334155} thead{background:#0f172a} th{padding:12px 16px;text-align:left;font-size:12px;color:#64748b;font-weight:600;letter-spacing:.05em;text-transform:uppercase} td{padding:11px 16px;border-top:1px solid #1e293b2a;font-size:14px;vertical-align:middle} tr:nth-child(odd){background:#243044} tr:hover{background:#2d3f55} .loc{color:#e2e8f0;font-weight:500} .isp{display:inline-block;padding:2px 8px;border-radius:20px;font-size:11px;font-weight:600} .isp-1{background:#1d4ed820;color:#60a5fa} .isp-2{background:#16a34a20;color:#4ade80} .isp-3{background:#d9770620;color:#fb923c} .isp-4{background:#7c3aed20;color:#a78bfa} .bar-wrap{width:100px;margin-bottom:3px} .badge{display:inline-block;padding:1px 6px;border-radius:4px;font-size:11px;margin-left:6px} .code{display:inline-block;padding:2px 8px;border-radius:4px;font-size:12px;font-weight:700} .ok{background:#16a34a20;color:#4ade80} .err{background:#dc262620;color:#f87171} .url-box{background:#1e293b;border:1px solid #334155;border-radius:10px;padding:14px 18px;margin-bottom:20px;font-size:14px;display:flex;gap:12px;align-items:center} .url-box .u{color:#38bdf8;word-break:break-all} .url-box .t{color:#64748b;font-size:12px;white-space:nowrap} footer{text-align:center;color:#334155;font-size:12px;margin-top:24px} </style> </head> <body> <h1>🚀 17CE 网站测速报告</h1> <p class="subtitle">由 17CE WebSocket API 生成 · 2026-03-20 11:29:08</p> <div class="url-box"> <span style="color:#64748b">目标</span> <span class="u">http://www.baidu.com</span> <span class="t">2026-03-20 11:29:08</span> </div> <div class="cards"> <div class="card"> <div class="val green">85 <span style="font-size:14px;font-weight:400">ms</span></div> <div class="lbl">平均响应时间</div> </div> <div class="card"> <div class="val green">23 <span style="font-size:14px;font-weight:400">ms</span></div> <div class="lbl">最快节点</div> </div> <div class="card"> <div class="val yellow">217 <span style="font-size:14px;font-weight:400">ms</span></div> <div class="lbl">最慢节点</div> </div> <div class="card"> <div class="val blue">7/7</div> <div class="lbl">成功 / 总节点</div> </div> </div> <table> <thead> <tr> <th>节点位置</th> <th>运营商</th> <th>总耗时 (ms)</th> <th>首字节 (ms)</th> <th>DNS (ms)</th> <th>HTTP</th> <th>解析IP</th> </tr> </thead> <tbody> <tr> <td><span class="loc">中国江苏南京联通</span></td> <td><span class="isp isp-2">联通</span></td> <td> <div class="bar-wrap"><div style="width:9%;background:#22c55e;height:8px;border-radius:4px;min-width:4px"></div></div> <span style="color:#22c55e;font-weight:600">23.0</span> <span class="badge" style="background:#22c55e20;color:#22c55e">极快</span> </td> <td style="color:#94a3b8">22.4</td> <td style="color:#94a3b8">2.6</td> <td><span class="code ok">200</span></td> <td style="color:#64748b;font-size:12px">153.3.238.28</td> </tr> <tr> <td><span class="loc">中国江苏电信</span></td> <td><span class="isp isp-1">电信</span></td> <td> <div class="bar-wrap"><div style="width:13%;background:#22c55e;height:8px;border-radius:4px;min-width:4px"></div></div> <span style="color:#22c55e;font-weight:600">32.8</span> <span class="badge" style="background:#22c55e20;color:#22c55e">极快</span> </td> <td style="color:#94a3b8">32.4</td> <td style="color:#94a3b8">2.7</td> <td><span class="code ok">200</span></td> <td style="color:#64748b;font-size:12px">180.101.51.73</td> </tr> <tr> <td><span class="loc">中国江苏南京联通</span></td> <td><span class="isp isp-2">联通</span></td> <td> <div class="bar-wrap"><div style="width:24%;background:#eab308;height:8px;border-radius:4px;min-width:4px"></div></div> <span style="color:#eab308;font-weight:600">59.2</span> <span class="badge" style="background:#eab30820;color:#eab308">快</span> </td> <td style="color:#94a3b8">58.7</td> <td style="color:#94a3b8">2.8</td> <td><span class="code ok">200</span></td> <td style="color:#64748b;font-size:12px">153.3.238.28</td> </tr> <tr> <td><span class="loc">中国广东广州电信</span></td> <td><span class="isp isp-1">电信</span></td> <td> <div class="bar-wrap"><div style="width:28%;background:#eab308;height:8px;border-radius:4px;min-width:4px"></div></div> <span style="color:#eab308;font-weight:600">67.9</span> <span class="badge" style="background:#eab30820;color:#eab308">快</span> </td> <td style="color:#94a3b8">67.6</td> <td style="color:#94a3b8">13.1</td> <td><span class="code ok">200</span></td> <td style="color:#64748b;font-size:12px">183.2.172.17</td> </tr> <tr> <td><span class="loc">中国广东广州电信</span></td> <td><span class="isp isp-1">电信</span></td> <td> <div class="bar-wrap"><div style="width:40%;background:#eab308;height:8px;border-radius:4px;min-width:4px"></div></div> <span style="color:#eab308;font-weight:600">97.0</span> <span class="badge" style="background:#eab30820;color:#eab308">快</span> </td> <td style="color:#94a3b8">96.5</td> <td style="color:#94a3b8">2.5</td> <td><span class="code ok">200</span></td> <td style="color:#64748b;font-size:12px">183.2.172.17</td> </tr> <tr> <td><span class="loc">中国广东广州电信</span></td> <td><span class="isp isp-1">电信</span></td> <td> <div class="bar-wrap"><div style="width:40%;background:#eab308;height:8px;border-radius:4px;min-width:4px"></div></div> <span style="color:#eab308;font-weight:600">97.6</span> <span class="badge" style="background:#eab30820;color:#eab308">快</span> </td> <td style="color:#94a3b8">97.3</td> <td style="color:#94a3b8">35.7</td> <td><span class="code ok">200</span></td> <td style="color:#64748b;font-size:12px">183.2.172.17</td> </tr> <tr> <td><span class="loc">中国江苏南京联通</span></td> <td><span class="isp isp-2">联通</span></td> <td> <div class="bar-wrap"><div style="width:90%;background:#ef4444;height:8px;border-radius:4px;min-width:4px"></div></div> <span style="color:#ef4444;font-weight:600">217.2</span> <span class="badge" style="background:#ef444420;color:#ef4444">慢</span> </td> <td style="color:#94a3b8">216.8</td> <td style="color:#94a3b8">159.3</td> <td><span class="code ok">200</span></td> <td style="color:#64748b;font-size:12px">153.3.238.127</td> </tr></tbody> </table> <footer>17CE WebSocket API · 7 个节点 · 2026-03-20 11:29:08</footer> </body> </html> FILE:report_demo_v2.html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"> <title>17CE 测速报告</title> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); *{box-sizing:border-box;margin:0;padding:0} body{font-family:'Inter',system-ui,-apple-system,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;padding:24px 16px;line-height:1.5} .container{max-width:1000px;margin:0 auto} /* 头部区域 */ .header{margin-bottom:24px} h1{font-size:22px;font-weight:700;background:linear-gradient(135deg,#38bdf8,#818cf8);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:8px} .url-box{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:14px 16px;font-size:14px;display:flex;flex-wrap:wrap;gap:8px 12px;align-items:center;word-break:break-all} .url-box .u{color:#38bdf8;font-weight:500;flex:1 1 100%} .url-box .t{color:#64748b;font-size:12px} /* 统计卡片 */ .cards{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-bottom:24px} @media(min-width:600px){ .cards{grid-template-columns:repeat(4,1fr);gap:16px} .url-box .u{flex:1} } .card{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:16px} .card .val{font-size:24px;font-weight:700;line-height:1.2} .card .unit{font-size:13px;font-weight:400;opacity:0.7;margin-left:2px} .card .lbl{font-size:12px;color:#94a3b8;margin-top:4px} .green{color:#22c55e}.yellow{color:#eab308}.red{color:#ef4444}.blue{color:#38bdf8} /* 图表区域 */ .chart-box{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:20px;margin-bottom:24px} .chart-title{font-size:14px;font-weight:600;color:#cbd5e1;margin-bottom:16px} .chart-row{display:flex;align-items:center;margin-bottom:10px;font-size:12px} .chart-lbl{width:90px;flex-shrink:0;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:8px} .chart-bar-wrap{flex:1;display:flex;align-items:center;gap:8px} .chart-bar{height:10px;border-radius:5px;min-width:4px;transition:width 0.5s ease-out} .chart-val{font-weight:600} @media(min-width:600px){ .chart-lbl{width:120px;font-size:13px} .chart-bar{height:12px;border-radius:6px} } /* 表格区域 (移动端适配) */ .table-wrap{background:#1e293b;border:1px solid #334155;border-radius:12px;overflow-x:auto;-webkit-overflow-scrolling:touch} table{width:100%;min-width:600px;border-collapse:collapse;text-align:left} thead{background:#0f172a80} th{padding:14px 16px;font-size:12px;color:#94a3b8;font-weight:600;white-space:nowrap} td{padding:14px 16px;border-top:1px solid #33415580;font-size:14px;vertical-align:middle;white-space:nowrap} tr:hover td{background:#fbbf240a} .loc{color:#f8fafc;font-weight:500} .bar-wrap{width:80px;background:#334155;border-radius:4px;overflow:hidden;margin-bottom:4px} .bar-fill{height:4px} .badge{display:inline-block;padding:2px 6px;border-radius:4px;font-size:11px;margin-left:6px;vertical-align:middle} .code{display:inline-block;padding:2px 8px;border-radius:6px;font-size:12px;font-weight:700} .ok{background:#22c55e20;color:#4ade80} .err{background:#ef444420;color:#f87171} .fade{color:#64748b} .ip-col{font-size:13px} footer{text-align:center;color:#475569;font-size:12px;margin-top:32px;padding-bottom:16px} </style> </head> <body> <div class="container"> <div class="header"> <h1>🚀 17CE 测速报告</h1> <div class="url-box"> <span class="fade">目标</span> <span class="u">http://example.com/api/test</span> <span class="t">2026-03-20 11:42:09</span> </div> </div> <div class="cards"> <div class="card"> <div class="val yellow">139<span class="unit">ms</span></div> <div class="lbl">平均响应时间</div> </div> <div class="card"> <div class="val green">45<span class="unit">ms</span></div> <div class="lbl">最快节点</div> </div> <div class="card"> <div class="val yellow">250<span class="unit">ms</span></div> <div class="lbl">最慢节点</div> </div> <div class="card"> <div class="val red">3 / 5</div> <div class="lbl">连通节点数</div> </div> </div> <div class="chart-box"> <div class="chart-title">📊 节点响应时间对比 (越短越好)</div> <div class="chart-row"> <div class="chart-lbl">北京联通</div> <div class="chart-bar-wrap"> <div class="chart-bar" style="width:16%; background:#22c55e"></div> <div class="chart-val" style="color:#22c55e">45.2ms</div> </div> </div> <div class="chart-row"> <div class="chart-lbl">上海电信</div> <div class="chart-bar-wrap"> <div class="chart-bar" style="width:43%; background:#eab308"></div> <div class="chart-val" style="color:#eab308">120.5ms</div> </div> </div> <div class="chart-row"> <div class="chart-lbl">广东移动</div> <div class="chart-bar-wrap"> <div class="chart-bar" style="width:90%; background:#f97316"></div> <div class="chart-val" style="color:#f97316">250.0ms</div> </div> </div> </div> <div class="table-wrap"> <table> <thead> <tr> <th>节点位置</th> <th>总耗时 (ms)</th> <th>首字节 (ms)</th> <th>DNS (ms)</th> <th>HTTP</th> <th>解析IP</th> </tr> </thead> <tbody> <tr> <td><span class="loc">北京联通</span></td> <td> <div class="bar-wrap"><div class="bar-fill" style="width:16%;background:#22c55e"></div></div> <span style="color:#22c55e;font-weight:600">45.2</span> <span class="badge" style="background:#22c55e20;color:#22c55e">极快</span> </td> <td class="fade">44.1</td> <td class="fade">12.0</td> <td><span class="code ok">200</span></td> <td class="fade ip-col">1.1.1.1</td> </tr> <tr> <td><span class="loc">上海电信</span></td> <td> <div class="bar-wrap"><div class="bar-fill" style="width:43%;background:#eab308"></div></div> <span style="color:#eab308;font-weight:600">120.5</span> <span class="badge" style="background:#eab30820;color:#eab308">正常</span> </td> <td class="fade">110.1</td> <td class="fade">8.5</td> <td><span class="code ok">200</span></td> <td class="fade ip-col">2.2.2.2</td> </tr> <tr> <td><span class="loc">广东移动</span></td> <td> <div class="bar-wrap"><div class="bar-fill" style="width:90%;background:#f97316"></div></div> <span style="color:#f97316;font-weight:600">250.0</span> <span class="badge" style="background:#f9731620;color:#f97316">慢</span> </td> <td class="fade">240.0</td> <td class="fade">30.0</td> <td><span class="code ok">200</span></td> <td class="fade ip-col">3.3.3.3</td> </tr> <tr> <td><span class="loc">海外多线</span></td> <td> <div class="bar-wrap"><div class="bar-fill" style="width:100%;background:#ef444455"></div></div> <span style="color:#ef4444;font-weight:600">--</span> <span class="badge" style="background:#ef444420;color:#ef4444">错误 503</span> </td> <td class="fade">--</td> <td class="fade">--</td> <td><span class="code err">503</span></td> <td class="fade ip-col">8.8.8.8</td> </tr> <tr> <td><span class="loc">新疆电信</span></td> <td> <div class="bar-wrap"><div class="bar-fill" style="width:100%;background:#ef444455"></div></div> <span style="color:#ef4444;font-weight:600">--</span> <span class="badge" style="background:#ef444420;color:#ef4444">超时/阻断</span> </td> <td class="fade">--</td> <td class="fade">--</td> <td><span class="code err">-</span></td> <td class="fade ip-col">-</td> </tr></tbody> </table> </div> <footer>17CE WebSocket API · 5 节点 · 由 OpenClaw 生成</footer> </div> </body> </html> FILE:report_demo_v3.html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"> <title>17CE 测速报告</title> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); *{box-sizing:border-box;margin:0;padding:0} body{font-family:'Inter',system-ui,-apple-system,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;padding:24px 16px;line-height:1.5} .container{max-width:1000px;margin:0 auto} /* 头部区域 */ .header{margin-bottom:20px} h1{font-size:20px;font-weight:700;background:linear-gradient(135deg,#38bdf8,#818cf8);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:12px} .url-box{background:#1e293b;border:1px solid #334155;border-radius:10px;padding:10px 14px;font-size:13px;word-break:break-all} .url-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px} .url-top .lbl{color:#64748b;font-size:12px} .url-top .t{color:#64748b;font-size:11px} .url-bot .u{color:#38bdf8;font-weight:500;line-height:1.4} /* 统计卡片 */ .cards{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-bottom:20px} @media(min-width:600px){ .cards{grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:24px} h1{font-size:22px;margin-bottom:16px} .url-box{display:flex;align-items:center;gap:12px;padding:14px 16px} .url-top{margin-bottom:0;display:inline-flex;gap:12px} .url-bot{flex:1} .url-top .t{margin-left:auto} } .card{background:#1e293b;border:1px solid #334155;border-radius:10px;padding:14px} .card .val{font-size:22px;font-weight:700;line-height:1.2} .card .unit{font-size:12px;font-weight:400;opacity:0.7;margin-left:2px} .card .lbl{font-size:12px;color:#94a3b8;margin-top:4px} .green{color:#22c55e}.yellow{color:#eab308}.red{color:#ef4444}.blue{color:#38bdf8} /* 图表区域 */ .chart-box{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:20px;margin-bottom:24px} .chart-title{font-size:14px;font-weight:600;color:#cbd5e1;margin-bottom:16px} .chart-row{display:flex;align-items:center;margin-bottom:10px;font-size:12px} .chart-lbl{width:90px;flex-shrink:0;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:8px} .chart-bar-wrap{flex:1;display:flex;align-items:center;gap:8px} .chart-bar{height:10px;border-radius:5px;min-width:4px;transition:width 0.5s ease-out} .chart-val{font-weight:600} @media(min-width:600px){ .chart-lbl{width:120px;font-size:13px} .chart-bar{height:12px;border-radius:6px} } /* 表格区域 (移动端适配) */ .table-wrap{background:#1e293b;border:1px solid #334155;border-radius:12px;overflow-x:auto;-webkit-overflow-scrolling:touch} table{width:100%;min-width:600px;border-collapse:collapse;text-align:left} thead{background:#0f172a80} th{padding:14px 16px;font-size:12px;color:#94a3b8;font-weight:600;white-space:nowrap} td{padding:14px 16px;border-top:1px solid #33415580;font-size:14px;vertical-align:middle;white-space:nowrap} tr:hover td{background:#fbbf240a} .loc{color:#f8fafc;font-weight:500} .bar-wrap{width:80px;background:#334155;border-radius:4px;overflow:hidden;margin-bottom:4px} .bar-fill{height:4px} .badge{display:inline-block;padding:2px 6px;border-radius:4px;font-size:11px;margin-left:6px;vertical-align:middle} .code{display:inline-block;padding:2px 8px;border-radius:6px;font-size:12px;font-weight:700} .ok{background:#22c55e20;color:#4ade80} .err{background:#ef444420;color:#f87171} .fade{color:#64748b} .ip-col{font-size:13px} footer{text-align:center;color:#475569;font-size:12px;margin-top:32px;padding-bottom:16px} </style> </head> <body> <div class="container"> <div class="header"> <h1>🚀 17CE 测速报告</h1> <div class="url-box"> <div class="url-top"> <span class="lbl">目标</span> <span class="t">2026-03-20 11:51:41</span> </div> <div class="url-bot"> <span class="u">http://example.com/api/test</span> </div> </div> </div> <div class="cards"> <div class="card"> <div class="val yellow">139<span class="unit">ms</span></div> <div class="lbl">平均响应时间</div> </div> <div class="card"> <div class="val green">45<span class="unit">ms</span></div> <div class="lbl">最快节点</div> </div> <div class="card"> <div class="val yellow">250<span class="unit">ms</span></div> <div class="lbl">最慢节点</div> </div> <div class="card"> <div class="val blue">3 / 3</div> <div class="lbl">连通节点数</div> </div> </div> <div class="chart-box"> <div class="chart-title">📊 节点响应时间对比 (越短越好)</div> <div class="chart-row"> <div class="chart-lbl">北京联通</div> <div class="chart-bar-wrap"> <div class="chart-bar" style="width:16%; background:#22c55e"></div> <div class="chart-val" style="color:#22c55e">45.2ms</div> </div> </div> <div class="chart-row"> <div class="chart-lbl">上海电信</div> <div class="chart-bar-wrap"> <div class="chart-bar" style="width:43%; background:#eab308"></div> <div class="chart-val" style="color:#eab308">120.5ms</div> </div> </div> <div class="chart-row"> <div class="chart-lbl">广东移动</div> <div class="chart-bar-wrap"> <div class="chart-bar" style="width:90%; background:#f97316"></div> <div class="chart-val" style="color:#f97316">250.0ms</div> </div> </div> </div> <div class="table-wrap"> <table> <thead> <tr> <th>节点位置</th> <th>总耗时 (ms)</th> <th>首字节 (ms)</th> <th>DNS (ms)</th> <th>HTTP</th> <th>解析IP</th> </tr> </thead> <tbody> <tr> <td><span class="loc">北京联通</span></td> <td> <div class="bar-wrap"><div class="bar-fill" style="width:16%;background:#22c55e"></div></div> <span style="color:#22c55e;font-weight:600">45.2</span> <span class="badge" style="background:#22c55e20;color:#22c55e">极快</span> </td> <td class="fade">44.1</td> <td class="fade">12.0</td> <td><span class="code ok">200</span></td> <td class="fade ip-col">1.1.1.1</td> </tr> <tr> <td><span class="loc">上海电信</span></td> <td> <div class="bar-wrap"><div class="bar-fill" style="width:43%;background:#eab308"></div></div> <span style="color:#eab308;font-weight:600">120.5</span> <span class="badge" style="background:#eab30820;color:#eab308">正常</span> </td> <td class="fade">110.1</td> <td class="fade">8.5</td> <td><span class="code ok">200</span></td> <td class="fade ip-col">2.2.2.2</td> </tr> <tr> <td><span class="loc">广东移动</span></td> <td> <div class="bar-wrap"><div class="bar-fill" style="width:90%;background:#f97316"></div></div> <span style="color:#f97316;font-weight:600">250.0</span> <span class="badge" style="background:#f9731620;color:#f97316">慢</span> </td> <td class="fade">240.0</td> <td class="fade">30.0</td> <td><span class="code ok">200</span></td> <td class="fade ip-col">3.3.3.3</td> </tr></tbody> </table> </div> <footer>17CE WebSocket API · 3 节点 · 由 OpenClaw 生成</footer> </div> </body> </html> FILE:requirements.txt websockets>=10.4 requests>=2.28.0 FILE:scripts/report.py #!/usr/bin/env python3 """ 17CE 测速结果 HTML 报表生成器 (支持移动端 + 图表对比) 用法:python report.py results.json > report.html 或在 speedtest_ws.py 的 --html 模式下直接生成 """ import json import sys from datetime import datetime def speed_color(ms: float, code: int) -> str: if code != 200 and code != 301 and code != 302: return "#ef4444" # 红色 (错误) if ms < 50: return "#22c55e" # 绿 if ms < 150: return "#eab308" # 黄 return "#f97316" # 橙 def speed_label(ms: float, code: int) -> str: if code != 200 and code != 301 and code != 302: if code == 0: return "超时/阻断" return f"错误 {code}" if ms < 50: return "极快" if ms < 100: return "快" if ms < 200: return "正常" if ms < 500: return "慢" return "极慢" def generate_html(results: list, url: str = "", test_time: str = "") -> str: if not test_time: test_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") valid_results = [r for r in results if r.get("HttpCode") in (200, 301, 302) and r.get("TotalTime_ms")] err_results = [r for r in results if r not in valid_results] all_total = [r["TotalTime_ms"] for r in valid_results] avg_ms = sum(all_total) / len(all_total) if all_total else 0 min_ms = min(all_total) if all_total else 0 max_ms = max(all_total) if all_total else 0 success = len(valid_results) total_nodes = len(results) max_bar = max_ms * 1.1 if max_ms > 0 else 500 # 生成图表和表格行 (按耗时排序) sorted_results = sorted(valid_results, key=lambda x: x.get("TotalTime_ms", 9999)) + err_results chart_html = "" table_rows = "" for r in sorted_results: code = r.get("HttpCode", 0) total = r.get("TotalTime_ms", 0) ttfb = r.get("TTFBTime_ms", 0) dns = r.get("DNS_ms", 0) prov = r.get("Province", "未知") isp = r.get("ISP", "未知") loc = f"{prov}{isp}" color = speed_color(total, code) label = speed_label(total, code) is_ok = code in (200, 301, 302) code_cls = "ok" if is_ok else "err" # 图表和表格共用的进度条属性 pct = min(int(total / max_bar * 100), 100) if is_ok and max_bar > 0 else (100 if not is_ok else 0) bar_bg = color if is_ok else "#ef444455" # 图表条目 (仅展示成功连接的节点) if is_ok: display_val = f"{total:.1f}ms" chart_html += f""" <div class="chart-row"> <div class="chart-lbl">{loc}</div> <div class="chart-bar-wrap"> <div class="chart-bar" style="width:{pct}%; background:{color}"></div> <div class="chart-val" style="color:{color}">{display_val}</div> </div> </div>""" # 表格行 bar_width = f"width:{pct}%" if is_ok else "width:100%" table_rows += f""" <tr> <td><span class="loc">{loc}</span></td> <td> <div class="bar-wrap"><div class="bar-fill" style="{bar_width};background:{bar_bg}"></div></div> <span style="color:{color};font-weight:600">{f"{total:.1f}" if is_ok else '--'}</span> <span class="badge" style="background:{color}20;color:{color}">{label}</span> </td> <td class="fade">{f"{ttfb:.1f}" if is_ok else '--'}</td> <td class="fade">{f"{dns:.1f}" if is_ok else '--'}</td> <td><span class="code {code_cls}">{code if code > 0 else '-'}</span></td> <td class="fade ip-col">{r.get('SrcIP','-')}</td> </tr>""" avg_color = 'green' if avg_ms < 100 else 'yellow' if avg_ms < 300 else 'red' max_color = 'red' if max_ms > 300 else 'yellow' return f"""<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"> <title>17CE 测速报告</title> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); *{{box-sizing:border-box;margin:0;padding:0}} body{{font-family:'Inter',system-ui,-apple-system,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;padding:24px 16px;line-height:1.5}} .container{{max-width:1000px;margin:0 auto}} /* 头部区域 */ .header{{margin-bottom:20px}} h1{{font-size:20px;font-weight:700;background:linear-gradient(135deg,#38bdf8,#818cf8);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:12px}} .url-box{{background:#1e293b;border:1px solid #334155;border-radius:10px;padding:10px 14px;font-size:13px;word-break:break-all}} .url-top{{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}} .url-top .lbl{{color:#64748b;font-size:12px}} .url-top .t{{color:#64748b;font-size:11px}} .url-bot .u{{color:#38bdf8;font-weight:500;line-height:1.4}} /* 统计卡片 */ .cards{{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-bottom:20px}} @media(min-width:600px){{ .cards{{grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:24px}} h1{{font-size:22px;margin-bottom:16px}} .url-box{{display:flex;align-items:center;gap:12px;padding:14px 16px}} .url-top{{margin-bottom:0;display:inline-flex;gap:12px}} .url-bot{{flex:1}} .url-top .t{{margin-left:auto}} }} .card{{background:#1e293b;border:1px solid #334155;border-radius:10px;padding:14px}} .card .val{{font-size:22px;font-weight:700;line-height:1.2}} .card .unit{{font-size:12px;font-weight:400;opacity:0.7;margin-left:2px}} .card .lbl{{font-size:12px;color:#94a3b8;margin-top:4px}} .green{{color:#22c55e}}.yellow{{color:#eab308}}.red{{color:#ef4444}}.blue{{color:#38bdf8}} /* 图表区域 */ .chart-box{{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:20px;margin-bottom:24px}} .chart-title{{font-size:14px;font-weight:600;color:#cbd5e1;margin-bottom:16px}} .chart-row{{display:flex;align-items:center;margin-bottom:10px;font-size:12px}} .chart-lbl{{width:90px;flex-shrink:0;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:8px}} .chart-bar-wrap{{flex:1;display:flex;align-items:center;gap:8px}} .chart-bar{{height:10px;border-radius:5px;min-width:4px;transition:width 0.5s ease-out}} .chart-val{{font-weight:600}} @media(min-width:600px){{ .chart-lbl{{width:120px;font-size:13px}} .chart-bar{{height:12px;border-radius:6px}} }} /* 表格区域 (移动端适配) */ .table-wrap{{background:#1e293b;border:1px solid #334155;border-radius:12px;overflow-x:auto;-webkit-overflow-scrolling:touch}} table{{width:100%;min-width:600px;border-collapse:collapse;text-align:left}} thead{{background:#0f172a80}} th{{padding:14px 16px;font-size:12px;color:#94a3b8;font-weight:600;white-space:nowrap}} td{{padding:14px 16px;border-top:1px solid #33415580;font-size:14px;vertical-align:middle;white-space:nowrap}} tr:hover td{{background:#fbbf240a}} .loc{{color:#f8fafc;font-weight:500}} .bar-wrap{{width:80px;background:#334155;border-radius:4px;overflow:hidden;margin-bottom:4px}} .bar-fill{{height:4px}} .badge{{display:inline-block;padding:2px 6px;border-radius:4px;font-size:11px;margin-left:6px;vertical-align:middle}} .code{{display:inline-block;padding:2px 8px;border-radius:6px;font-size:12px;font-weight:700}} .ok{{background:#22c55e20;color:#4ade80}} .err{{background:#ef444420;color:#f87171}} .fade{{color:#64748b}} .ip-col{{font-size:13px}} footer{{text-align:center;color:#475569;font-size:12px;margin-top:32px;padding-bottom:16px}} </style> </head> <body> <div class="container"> <div class="header"> <h1>🚀 17CE 测速报告</h1> <div class="url-box"> <div class="url-top"> <span class="lbl">目标</span> <span class="t">{test_time}</span> </div> <div class="url-bot"> <span class="u">{url or "未指定"}</span> </div> </div> </div> <div class="cards"> <div class="card"> <div class="val {avg_color}">{avg_ms:.0f}<span class="unit">ms</span></div> <div class="lbl">平均响应时间</div> </div> <div class="card"> <div class="val green">{min_ms:.0f}<span class="unit">ms</span></div> <div class="lbl">最快节点</div> </div> <div class="card"> <div class="val {max_color}">{max_ms:.0f}<span class="unit">ms</span></div> <div class="lbl">最慢节点</div> </div> <div class="card"> <div class="val {'blue' if success==total_nodes else 'red'}">{success} / {total_nodes}</div> <div class="lbl">连通节点数</div> </div> </div> <div class="chart-box"> <div class="chart-title">📊 节点响应时间对比 (越短越好)</div> {chart_html} </div> <div class="table-wrap"> <table> <thead> <tr> <th>节点位置</th> <th>总耗时 (ms)</th> <th>首字节 (ms)</th> <th>DNS (ms)</th> <th>HTTP</th> <th>解析IP</th> </tr> </thead> <tbody>{table_rows}</tbody> </table> </div> <footer>17CE WebSocket API · {total_nodes} 节点 · 由 OpenClaw 生成</footer> </div> </body> </html>""" if __name__ == "__main__": if len(sys.argv) > 1: with open(sys.argv[1]) as f: data = json.load(f) results = data if isinstance(data, list) else data.get("results", []) url = data.get("url", "") if isinstance(data, dict) else "" else: # allow piping json content = sys.stdin.read() try: data = json.loads(content) results = data if isinstance(data, list) else data.get("results", []) url = data.get("url", "") if isinstance(data, dict) else "" except: results = [] url = "" print(generate_html(results, url)) FILE:scripts/speedtest.py #!/usr/bin/env python3 """ 17CE 网站测速脚本 用法:python speedtest.py <url> [选项] 支持两种认证方式: 1. 账号密码 (appKey/appSecret):调用 /api/site/http 2. api_pwd Code 认证:调用 /apis/http(推荐) 示例: # 使用 api_pwd(推荐,不需要登录密码) python speedtest.py http://example.com --apikey [email protected] --apipwd OGIBKI978JEZZO2F # 使用账号密码 python speedtest.py http://example.com --user admin --pass secret """ import argparse import hashlib import base64 import json import sys import time import requests import warnings # 忽略OpenSSL警告 warnings.filterwarnings("ignore", category=DeprecationWarning) LEGACY_API_BASE = "http://www.17ce.com/api/site" API_BASE = "http://www.17ce.com/apis" def gen_code(api_pwd: str, username: str, timestamp: int) -> str: """ 根据 api_pwd 生成认证 code 算法:md5(base64(substr(md5(api_pwd), 4, 19) + username + timestamp)) 与 ApisController::check_api 中 $decrypt2 逻辑一致 """ api_pwd_md5 = hashlib.md5(api_pwd.encode()).hexdigest() part = api_pwd_md5[4:23] # substr($md5, 4, 19) raw = part + username.strip() + str(timestamp) code = hashlib.md5(base64.b64encode(raw.encode())).hexdigest() return code def submit_with_apipwd(url: str, username: str, api_pwd: str, isp_list: list = None, area_list: list = None, sid_list: list = None, rt: int = 1, host: str = None, referer: str = None, cookie: str = None, agent: str = None, nocache: int = 0) -> dict: """ 使用 api_pwd Code 认证提交测速 → /apis/http """ t = int(time.time()) code = gen_code(api_pwd, username, t) data = [ ("user", username), ("t", str(t)), ("code", code), ("url", url), ("rt", str(rt)), ("nocache", str(nocache)), ] if sid_list: sid_str = ",".join(str(s) for s in sid_list[:3]) data.append(("sid", sid_str)) else: if isp_list is None: isp_list = [0] for isp in isp_list: data.append(("isp", str(isp))) if area_list: for area in area_list: data.append(("area", str(area))) if host: data.append(("host", host)) if referer: data.append(("referer", referer)) if cookie: data.append(("cookie", cookie)) if agent: data.append(("agent", agent)) try: response = requests.post( f"{API_BASE}/http", data=data, timeout=30 ) response.raise_for_status() result = response.json() except requests.exceptions.Timeout: return {"success": False, "message": "请求超时,请检查网络连接"} except requests.exceptions.ConnectionError as e: return {"success": False, "message": f"连接失败: {e}"} except (json.JSONDecodeError, ValueError) as e: return {"success": False, "message": f"响应解析失败: {e}"} except requests.exceptions.RequestException as e: return {"success": False, "message": f"请求错误: {e}"} return _parse_result(result) def submit_speedtest(url: str, username: str, password: str, isp_list: list = None, area_list: list = None, sid_list: list = None, rt: int = 1, host: str = None, referer: str = None, cookie: str = None, agent: str = None, nocache: int = 0) -> dict: """ 使用账号密码提交测速 → /api/site/http """ data = [ ("appKey", username), ("appSecret", password), ("url", url), ("rt", str(rt)), ("nocache", str(nocache)), ] if sid_list: sid_str = ",".join(str(s) for s in sid_list[:3]) data.append(("sid", sid_str)) else: if isp_list is None: isp_list = [0] for isp in isp_list: data.append(("isp[]", str(isp))) if area_list: for area in area_list: data.append(("area[]", str(area))) if host: data.append(("host", host)) if referer: data.append(("referer", referer)) if cookie: data.append(("cookie", cookie)) if agent: data.append(("agent", agent)) try: response = requests.post( f"{LEGACY_API_BASE}/http", data=data, timeout=30 ) response.raise_for_status() result = response.json() except requests.exceptions.Timeout: return {"success": False, "message": "请求超时"} except requests.exceptions.ConnectionError as e: return {"success": False, "message": f"连接失败: {e}"} except (json.JSONDecodeError, ValueError) as e: return {"success": False, "message": f"响应解析失败: {e}"} except requests.exceptions.RequestException as e: return {"success": False, "message": f"请求错误: {e}"} return _parse_result(result) def _parse_result(result: dict) -> dict: """解析API响应""" # api_pwd 接口的错误码在 result['rt']==false 时 if result.get("rt") is False or result.get("rt") == "false": code = str(result.get("code", "")) error_messages = { "10001": "用户名不能为空", "10002": "code不能为空", "10003": "时间戳不能为空", "10004": "时间戳超过有效范围(需在5分钟内)", "10005": "接口用户不存在", "10006": "接口未开通或被禁用", "10007": "非法请求(IP限制)", "10008": "用户验证失败(api_pwd 错误)", "10009": "积分不足,请充值", "10010": "测试URL不能为空", "10011": "ISP不能为空", "10012": "URL格式不正确", } msg = error_messages.get(code, result.get("error", str(result))) return {"success": False, "message": msg, "error_code": code} # 账号密码接口的错误码 if result.get("error_code"): error_messages = { "1001": "用户名或密码为空", "1002": "账户或密码错误", "3002": "测试过于频繁,请稍后再试", "3003": "1分钟内检测超过50次", "3004": "30分钟内检测超过500次", "3005": "24小时内检测超过1000次", "3006": "请选择ISP运营商类型", "3007": "没有找到符合条件的监测点", } code = str(result.get("error_code", "")) msg = error_messages.get(code, result.get("error_message", "未知错误")) return {"success": False, "message": msg, "error_code": code} # 成功:新接口返回的数据格式 if result.get("teststatus") == 1 or (result.get("rt") is True and result.get("data")): data = result.get("data", result) tid = data.get("tid", result.get("tid", "")) return { "success": True, "tid": tid, "id": data.get("id", result.get("id")), "result_url": f"https://www.17ce.com/v/{tid}", "message": "测速任务提交成功,约 60~120 秒后可查看结果" } return {"success": False, "message": result.get("message", str(result))} def main(): parser = argparse.ArgumentParser( description="17CE 网站测速工具", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" 认证方式(二选一): 1. api_pwd 方式(推荐):传 --apikey + --apipwd 2. 账号密码方式: 传 --user + --pass 示例: # 用 api_pwd(推荐) python speedtest.py http://example.com --apikey [email protected] --apipwd YOURKEY # 仅测电信+联通 python speedtest.py http://example.com --apikey [email protected] --apipwd KEY --isp 1 2 # 指定节点ID python speedtest.py http://example.com --apikey [email protected] --apipwd KEY --sid 1 3 58 # 用账号密码 python speedtest.py http://example.com --user admin --pass secret """ ) parser.add_argument("url", help="待测试的URL(含协议头)") # api_pwd 认证(推荐) auth_group = parser.add_argument_group("认证参数(二选一)") auth_group.add_argument("--apikey", help="17CE账号用户名(搭配 --apipwd 使用)") auth_group.add_argument("--apipwd", help="17CE API密码(api_pwd,推荐方式,不需要登录密码)") # 账号密码认证(兼容旧方式) auth_group.add_argument("--user", "-u", help="17CE账号用户名(搭配 --pass 使用)") auth_group.add_argument("--pass", "-p", dest="password", help="17CE账号密码") parser.add_argument("--isp", nargs="+", type=int, default=None, metavar="N", help="运营商 (0=全部, 1=电信, 2=联通, 3=移动)") parser.add_argument("--area", nargs="+", type=int, default=None, metavar="N", help="区域 (0=全部, 1=大陆, 2=港澳台, 3=海外)") parser.add_argument("--sid", nargs="+", type=int, default=None, metavar="N", help="指定节点ID(最多3个)") parser.add_argument("--rt", type=int, default=1, choices=[1, 2, 3], help="请求方式 (1=GET, 2=POST, 3=HEAD)") parser.add_argument("--host", help="自定义Host头") parser.add_argument("--referer", help="自定义Referer") parser.add_argument("--cookie", help="自定义Cookie") parser.add_argument("--agent", help="自定义User-Agent") parser.add_argument("--nocache", action="store_true", help="禁用缓存") parser.add_argument("--json", dest="output_json", action="store_true", help="以JSON格式输出") parser.add_argument("--wait", type=int, default=0, metavar="SECONDS", help="等待N秒后提示查看结果(0=不等待)") args = parser.parse_args() # 选择认证方式 if args.apikey and args.apipwd: result = submit_with_apipwd( url=args.url, username=args.apikey, api_pwd=args.apipwd, isp_list=args.isp, area_list=args.area, sid_list=args.sid, rt=args.rt, host=args.host, referer=args.referer, cookie=args.cookie, agent=args.agent, nocache=1 if args.nocache else 0, ) elif args.user and args.password: result = submit_speedtest( url=args.url, username=args.user, password=args.password, isp_list=args.isp, area_list=args.area, sid_list=args.sid, rt=args.rt, host=args.host, referer=args.referer, cookie=args.cookie, agent=args.agent, nocache=1 if args.nocache else 0, ) else: parser.error("请提供认证信息:--apikey + --apipwd 或 --user + --pass") if args.output_json: print(json.dumps(result, ensure_ascii=False, indent=2)) else: if result["success"]: print(f"✅ {result['message']}") print(f" 任务ID : {result['tid']}") print(f" 结果链接 : {result['result_url']}") if args.wait > 0: print(f"\n⏳ 等待 {args.wait} 秒...", end="", flush=True) time.sleep(args.wait) print(f"\n🔗 请访问查看结果:{result['result_url']}") else: print(f"❌ 测速失败: {result['message']}") if result.get("error_code"): print(f" 错误码: {result['error_code']}") sys.exit(1) if __name__ == "__main__": main() FILE:scripts/speedtest_ws.py #!/usr/bin/env python3 """ 17CE WebSocket 实时测速脚本 接口:wss://wsapi.17ce.com:8001/socket 认证:api_pwd + user(无IP限制,实时返回多节点结果) 依赖:pip install websockets 用法: python speedtest_ws.py http://example.com \\ --user [email protected] \\ --apipwd PVCYVIQEGF8Y6D1G 示例: # 北京+上海双省电信+联通 python speedtest_ws.py http://example.com --user [email protected] --apipwd KEY --pro-ids 221,49 # JSON 输出 python speedtest_ws.py http://example.com --user [email protected] --apipwd KEY --json """ import argparse import asyncio import base64 import hashlib import json import ssl import sys import time ISP_MAP = {"1": "电信", "2": "联通", "3": "移动", "4": "教育网", "5": "铁通", "6": "多线"} PROVINCE_MAP = { "221": "北京", "49": "上海", "335": "广东", "277": "浙江", "50": "四川", "280": "江苏", "246": "重庆", "233": "天津", "238": "河北", "262": "山东", "162": "河南", "131": "湖北", "147": "湖南", "202": "福建", "210": "安徽", "292": "江西", "55": "贵州", "116": "云南", "104": "黑龙江","36": "吉林", "85": "辽宁", "179": "陕西", "189": "甘肃", "18": "宁夏", "65": "青海", "26": "新疆", "21": "西藏", "73": "海南", "317": "广西", "100": "内蒙古", "126": "山西" } try: import websockets except ImportError: print("❌ 请先安装依赖:pip install websockets", file=sys.stderr) sys.exit(1) WS_URL = "wss://wsapi.17ce.com:8001/socket/" def _md5(s: str) -> str: return hashlib.md5(s.encode()).hexdigest() def _b64(s: str) -> str: return base64.b64encode(s.encode()).decode() def gen_code(api_pwd: str, user: str, ut: str) -> str: """生成认证 code:md5(base64(md5(api_pwd)[4:23] + user + ut))""" return _md5(_b64(_md5(api_pwd)[4:23] + user.strip() + ut)) def build_ws_url(user: str, api_pwd: str) -> str: ut = str(int(time.time())) code = gen_code(api_pwd, user, ut) return f"{WS_URL}?ut={ut}&code={code}&user={user}" def build_task(url: str, isp_list: list, area_list: list, num: int, pro_ids: str = "221,49", rt: str = "GET", host: str = None, cookie: str = None, agent: str = None) -> dict: task = { "txnid": int(time.time()), "nodetype": isp_list, "num": num, "Url": url, "TestType": "HTTP", "Host": host or "", "TimeOut": 20, "Request": rt, "NoCache": True, "Speed": 0, "Cookie": cookie or "", "Trace": False, "Referer": url, "UserAgent": agent or "curl/7.47.0", "FollowLocation": 2, "GetMD5": False, "GetResponseHeader": False, "MaxDown": 1048576, "AutoDecompress": True, "type": 1, "isps": isp_list, "pro_ids": pro_ids, # 必须是字符串格式,如 "221,49" "areas": area_list, "PingSize": 32, "PingCount": 10, } return task async def run_speedtest(ws_url: str, task: dict, timeout: int, output_json: bool) -> list: results = [] node_info_map = {} try: async with websockets.connect(ws_url, open_timeout=15) as ws: if not output_json: print(f"✅ 已连接,等待认证...") logged_in = False start = time.time() header_printed = False while time.time() - start < timeout: try: raw = await asyncio.wait_for(ws.recv(), timeout=5) except asyncio.TimeoutError: if logged_in and time.time() - start > 30: break continue try: data = json.loads(raw) except json.JSONDecodeError: continue # 收到 login ok 后才发任务 if not logged_in and data.get("rt") == 1 and data.get("msg") == "login ok": logged_in = True await ws.send(json.dumps(task, ensure_ascii=False)) if not output_json: print(f"🚀 测速任务已发送,等待节点响应...\n") continue # 任务被接受:显示节点总数并保存节点省份/ISP映射 if data.get("type") == "TaskAccept": task_data = data.get("data", {}) for tid, info in task_data.items(): nodes = info.get("nodes", {}) for nid, ninfo in nodes.items(): ni = ninfo.get("nodeinfo", {}) node_info_map[str(nid)] = { "isp": ni.get("isp"), "pro_id": ni.get("pro_id"), "ip": ni.get("ip") } count = info.get("nodeCount", "?") if not output_json: print(f" 任务ID: {info.get('TaskId', tid)} 节点数: {count}") print(f"\n{'节点ID':<10} {'省份':<6} {'ISP':<4} {'总耗时ms':<10} {'首字节ms':<10} {'DNS ms':<8} {'HTTP'}") print("-" * 65) header_printed = True continue # 新的测速结果 if data.get("type") == "NewData": d = data.get("data", {}) node_id = str(d.get("NodeID", "")) http_code = d.get("HttpCode", 0) total = d.get("TotalTime", 0) * 1000 # 秒→毫秒 ttfb = d.get("TTFBTime", 0) * 1000 dns = d.get("NsLookup", 0) * 1000 src_ip = d.get("SrcIP", "") ni = node_info_map.get(node_id, {}) prov = PROVINCE_MAP.get(str(ni.get("pro_id")), "未知") isp_name = ISP_MAP.get(str(ni.get("isp")), "未知") result = { "NodeID": node_id, "Province": prov, "ISP": isp_name, "HttpCode": http_code, "TotalTime_ms": round(total, 1), "TTFBTime_ms": round(ttfb, 1), "DNS_ms": round(dns, 1), "SrcIP": src_ip, "TaskId": d.get("TaskId", ""), } results.append(result) if not output_json: if not header_printed: print(f"\n{'节点ID':<10} {'省份':<6} {'ISP':<4} {'总耗时ms':<10} {'首字节ms':<10} {'DNS ms':<8} {'HTTP'}") print("-" * 65) header_printed = True print(f"{node_id:<10} {prov:<6} {isp_name:<4} {total:<10.1f} {ttfb:<10.1f} {dns:<8.1f} {http_code}") continue # 任务结束 if data.get("type") == "TaskEnd": d = data.get("data", {}) if not output_json: ok = d.get("TotalCountOK", 0) err = d.get("TotalCountErr", 0) tot = d.get("TotalCount", 0) print(f"\n✅ 测速完成:{tot} 个节点,成功 {ok},失败 {err}") break except websockets.exceptions.WebSocketException as e: if not output_json: print(f"❌ WebSocket 错误: {e}", file=sys.stderr) else: print(json.dumps({"success": False, "error": str(e)}, ensure_ascii=False)) sys.exit(1) except Exception as e: if not output_json: print(f"❌ 错误: {e}", file=sys.stderr) else: print(json.dumps({"success": False, "error": str(e)}, ensure_ascii=False)) sys.exit(1) return results def main(): parser = argparse.ArgumentParser( description="17CE WebSocket 实时测速", formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("url", help="Target URL to test (include http:// or https://)") parser.add_argument("--user", required=True, help="17CE Account Email (Required)") parser.add_argument("--apipwd", required=True, help="17CE API PWD (Required)") parser.add_argument("--isp", nargs="+", type=int, default=[1, 2, 3], metavar="N", help="运营商 (1=电信 2=联通 3=移动),默认 1 2 3") parser.add_argument("--area", nargs="+", type=int, default=[1], metavar="N", help="区域 (1=大陆 2=港澳台 3=海外),默认 1") parser.add_argument("--num", type=int, default=2, help="每ISP节点数,默认 2") parser.add_argument("--pro-ids", default="221,49", help="省份ID字符串,逗号分隔,如 '221,49'(北京,上海),默认 '221,49'") parser.add_argument("--rt", default="GET", choices=["GET", "POST", "HEAD"], help="HTTP 请求方式,默认 GET") parser.add_argument("--host", help="自定义 Host 头") parser.add_argument("--cookie", help="自定义 Cookie") parser.add_argument("--agent", help="自定义 User-Agent") parser.add_argument("--timeout", type=int, default=60, help="超时秒数,默认 60") parser.add_argument("--json", dest="output_json", action="store_true", help="JSON 格式输出") parser.add_argument("--html", dest="output_html", action="store_true", help="HTML 格式输出") args = parser.parse_args() ws_url = build_ws_url(args.user, args.apipwd) task = build_task( url=args.url, isp_list=args.isp, area_list=args.area, num=args.num, pro_ids=args.pro_ids, rt=args.rt, host=args.host, cookie=args.cookie, agent=args.agent, ) if not args.output_json and not args.output_html: print(f"🌐 17CE WebSocket 测速") print(f" URL : {args.url}") print(f" ISP : {args.isp} 区域: {args.area} 节点/ISP: {args.num}") print(f" 省份 : {args.pro_ids}") results = asyncio.run(run_speedtest(ws_url, task, args.timeout, args.output_json or args.output_html)) if args.output_html: try: import report html = report.generate_html(results, args.url) print(html) except ImportError: print("❌ 缺少 report.py,无法生成 HTML 报表", file=sys.stderr) sys.exit(1) elif args.output_json: if results: avg = sum(r["TotalTime_ms"] for r in results) / len(results) print(json.dumps({ "success": True, "count": len(results), "avg_ms": round(avg, 1), "results": results }, ensure_ascii=False, indent=2)) else: print(json.dumps({"success": False, "error": "未收到测速数据"}, ensure_ascii=False)) else: if results: avg = sum(r["TotalTime_ms"] for r in results) / len(results) print(f" 平均响应时间: {avg:.0f} ms") if __name__ == "__main__": main() FILE:skill.yaml name: 17ce-speedtest description: > 17CE Global Website Speed Test Tool. Tests HTTP response time, Time to First Byte (TTFB), and DNS resolution time from monitoring nodes globally via WebSocket. Supports adaptive HTML reports for mobile/desktop UI and structured JSON output for downstream integration. version: 1.0.1 entry: scripts/speedtest_ws.py permissions: - network author: Huang Wangen