├── README.md └── _workers.js /README.md: -------------------------------------------------------------------------------- 1 | # V2.5版本,添加管理员登录参数,需要到CF worker环境变量里添加 ADMIN_PASSWORD,网页增加Token管理,登陆后可用 2 | 图片 3 | 图片 4 | 图片 5 | 6 | 7 | 8 | # Cloudflare 优选IP 收集器 9 | 由于GitHub版的被官方以滥用资源为理由封禁了项目,特推出基于Cloudflare worker版的优选IP,更快,更高效,更直观!抛弃github Action~ 10 | 11 |

12 | 13 | YouTube 14 | 15 |    16 | 17 | GitHub 18 | 19 |    20 | 21 | Telegram 22 | 23 |

24 | 25 | 一个基于 Cloudflare Workers 的优选 CF IP 地址收集与测速工具,自动从多个公开来源收集 Cloudflare IP 地址,并提供可视化界面和测速功能。 26 | 27 | ## 🌟 功能特点 28 | 29 | - **自动收集**:定时从多个公开来源自动收集 Cloudflare IP 地址 30 | - **智能测速**:内置一键测速功能,支持批量测试 IP 延迟 31 | - **多种格式**:支持 TXT 格式下载和原始数据获取 32 | - **ITDog 集成**:支持导出 IP 列表到 ITDog 进行批量 TCPing 测试 33 | - **现代化界面**:简洁美观的 Web 界面,支持响应式设计 34 | - **实时排序**:测速完成后自动按延迟排序,快速找到最优 IP 35 | 36 | 37 | ## 🚀 快速开始 38 | 39 | ### 前置要求 40 | 41 | - Cloudflare 账户 42 | - Workers 权限 43 | - KV 命名空间(用于存储 IP 数据) 44 | 45 | ### 部署步骤 46 | 47 | 1. **克隆项目** 48 | ```bash 49 | git clone https://github.com/your-username/cloudflare-ip-collector.git 50 | cd cloudflare-ip-collector 51 | ``` 52 | 53 | 2. **创建 KV 命名空间** 54 | - 在 Cloudflare Dashboard 中进入 Workers & Pages 55 | - 创建新的 KV 命名空间,名称建议为 `IP_STORAGE` 56 | - 记录下命名空间的 ID 57 | 58 | 3. **配置 Wrangler** 59 | - 复制 `wrangler.toml.example` 为 `wrangler.toml` 60 | - 更新 `wrangler.toml` 中的 KV 命名空间 ID: 61 | 62 | ```toml 63 | [[kv_namespaces]] 64 | binding = "IP_STORAGE" 65 | id = "your_kv_namespace_id_here" 66 | ``` 67 | 68 | 4. **部署到 Cloudflare** 69 | ```bash 70 | npm install 71 | npx wrangler deploy 72 | ``` 73 | 74 | 5. **配置定时任务** 75 | - 在 Cloudflare Dashboard 中为 Worker 添加定时触发器 76 | - 建议设置为每 12 小时运行一次 77 | - 修改定时更新操作如下 78 | - 登录Cloudflare Dashboard,进入Workers & Pages。 79 | - 选择您部署的Worker。 80 | - 点击“设置”选项卡,然后选择“触发器”。 81 | - 在“Cron 触发器”部分,点击“添加 Cron 触发器”。(推荐这种 简单) 82 | 图片 83 | 84 | 85 | - (另一种方式)输入Cron表达式:0 */12 * * * (每12小时执行一次) 86 | - 选择时区(例如:UTC)。 87 | - 点击“保存” 88 | - Cron 表达式 89 | - 推荐设置:0 */12 * * * (每 12 小时执行一次) 90 | - 其他常用选项: 91 | - 0 * * * * - 每小时执行一次 92 | - 0 0 * * * - 每天午夜执行 93 | - 0 */6 * * * - 每 6 小时执行一次 94 | 95 | ## 📖 使用方法 96 | 97 | ### Web 界面 98 | 99 | 访问部署后的 Worker 地址即可使用完整功能: 100 | 101 | - **查看 IP 列表**:浏览所有收集到的 Cloudflare IP 地址 102 | - **一键测速**:批量测试所有 IP 的延迟,自动排序 103 | - **导出数据**:下载 TXT 格式的 IP 列表 104 | - **ITDog 集成**:复制 IP 列表到 ITDog 进行更详细的测试 105 | 106 | ### API 接口 107 | 108 | - `GET /` - 主页面 109 | - `GET /ips` 或 `GET /ip.txt` - 获取纯文本 IP 列表 110 | - `GET /raw` - 获取原始 JSON 数据 111 | - `POST /update` - 手动触发 IP 更新 112 | - `GET /speedtest?ip=` - 测试指定 IP 的速度 113 | - `GET /itdog-data` - 获取 ITDog 格式数据 114 | 115 | ## ⚙️ 配置说明 116 | 117 | ### 数据来源 118 | 119 | 项目从多个公开的 Cloudflare IP 数据源自动收集,包括: 120 | 121 | - ip.164746.xyz 122 | - ip.haogege.xyz 123 | - stock.hostmonit.com/CloudFlareYes 124 | - api.uouin.com/cloudflare.html 125 | - addressesapi.090227.xyz 126 | - www.wetest.vip 127 | 128 | ### 环境变量 129 | 130 | 无需额外环境变量,所有配置通过代码管理。 131 | 132 | ## 🛠️ 开发 133 | 134 | ### 本地开发 135 | 136 | ```bash 137 | # 安装依赖 138 | npm install 139 | 140 | # 启动本地开发服务器 141 | npx wrangler dev 142 | 143 | # 部署到生产环境 144 | npx wrangler deploy 145 | ``` 146 | 147 | ### 项目结构 148 | 149 | ``` 150 | ├── cfip.js # 主 Worker 代码 151 | ├── wrangler.toml # Wrangler 配置 152 | ├── package.json # 项目依赖 153 | └── README.md # 项目说明 154 | ``` 155 | 156 | ## 📊 技术栈 157 | 158 | - **运行时**:Cloudflare Workers 159 | - **存储**:Cloudflare KV 160 | - **前端**:原生 HTML/CSS/JavaScript 161 | - **部署**:Wrangler 162 | 163 | ## 🤝 贡献 164 | 165 | 欢迎提交 Issue 和 Pull Request! 166 | 167 | 1. Fork 本项目 168 | 2. 创建特性分支 (`git checkout -b feature/AmazingFeature`) 169 | 3. 提交更改 (`git commit -m 'Add some AmazingFeature'`) 170 | 4. 推送到分支 (`git push origin feature/AmazingFeature`) 171 | 5. 创建 Pull Request 172 | 173 | ## 📄 开源协议 174 | 175 | 本项目基于 MIT 协议开源,详见 [LICENSE](LICENSE) 文件。 176 | 177 | ## ⚠️ 免责声明 178 | 179 | 本项目仅用于学习和研究目的,请勿用于商业用途或违反相关服务条款。使用者需自行承担相关风险。 180 | 181 | 182 | 如果这个项目对你有帮助,请给个 ⭐️ 支持一下! 183 | ## Star History 184 | 185 | 186 | -------------------------------------------------------------------------------- /_workers.js: -------------------------------------------------------------------------------- 1 | //V2.5版本,添加管理员登录参数,需要到CF worker环境变量里添加 ADMIN_PASSWORD,网页增加Token管理,登陆后可用 2 | // 自定义优质IP数量 3 | const FAST_IP_COUNT = 25; // 修改这个数字来自定义优质IP数量 4 | const AUTO_TEST_MAX_IPS = 200; // 自动测速的最大IP数量,避免测速过多导致超时 5 | 6 | export default { 7 | async scheduled(event, env, ctx) { 8 | console.log('Running scheduled IP update...'); 9 | 10 | try { 11 | if (!env.IP_STORAGE) { 12 | console.error('KV namespace IP_STORAGE is not bound'); 13 | return; 14 | } 15 | 16 | const startTime = Date.now(); 17 | const { uniqueIPs, results } = await updateAllIPs(env); 18 | const duration = Date.now() - startTime; 19 | 20 | await env.IP_STORAGE.put('cloudflare_ips', JSON.stringify({ 21 | ips: uniqueIPs, 22 | lastUpdated: new Date().toISOString(), 23 | count: uniqueIPs.length, 24 | sources: results 25 | })); 26 | 27 | // 自动触发测速并存储优质IP 28 | await autoSpeedTestAndStore(env, uniqueIPs); 29 | 30 | console.log(`Scheduled update: ${uniqueIPs.length} IPs collected in ${duration}ms`); 31 | } catch (error) { 32 | console.error('Scheduled update failed:', error); 33 | } 34 | }, 35 | 36 | async fetch(request, env, ctx) { 37 | const url = new URL(request.url); 38 | const path = url.pathname; 39 | 40 | // 检查 KV 是否绑定 41 | if (!env.IP_STORAGE) { 42 | return new Response('KV namespace IP_STORAGE is not bound. Please bind it in Worker settings.', { 43 | status: 500, 44 | headers: { 'Content-Type': 'text/plain' } 45 | }); 46 | } 47 | 48 | if (request.method === 'OPTIONS') { 49 | return handleCORS(); 50 | } 51 | 52 | try { 53 | switch (path) { 54 | case '/': 55 | return await serveHTML(env, request); 56 | case '/update': 57 | if (request.method !== 'POST') { 58 | return jsonResponse({ error: 'Method not allowed' }, 405); 59 | } 60 | return await handleUpdate(env, request); 61 | case '/ips': 62 | return await handleGetIPs(env, request); 63 | case '/ip.txt': 64 | return await handleGetIPs(env, request); 65 | case '/raw': 66 | return await handleRawIPs(env, request); 67 | case '/speedtest': 68 | return await handleSpeedTest(request, env); 69 | case '/itdog-data': 70 | return await handleItdogData(env, request); 71 | case '/fast-ips': 72 | return await handleGetFastIPs(env, request); 73 | case '/fast-ips.txt': 74 | return await handleGetFastIPsText(env, request); 75 | case '/admin-login': 76 | return await handleAdminLogin(request, env); 77 | case '/admin-status': 78 | return await handleAdminStatus(env); 79 | case '/admin-logout': 80 | return await handleAdminLogout(env); 81 | case '/admin-token': 82 | return await handleAdminToken(request, env); 83 | default: 84 | return jsonResponse({ error: 'Endpoint not found' }, 404); 85 | } 86 | } catch (error) { 87 | console.error('Error:', error); 88 | return jsonResponse({ error: error.message }, 500); 89 | } 90 | } 91 | }; 92 | 93 | // 管理员登录处理 94 | async function handleAdminLogin(request, env) { 95 | if (request.method !== 'POST') { 96 | return jsonResponse({ error: 'Method not allowed' }, 405); 97 | } 98 | 99 | try { 100 | const { password } = await request.json(); 101 | 102 | if (!env.ADMIN_PASSWORD) { 103 | return jsonResponse({ 104 | success: false, 105 | error: '管理员密码未配置,请在环境变量中设置 ADMIN_PASSWORD' 106 | }, 400); 107 | } 108 | 109 | if (password === env.ADMIN_PASSWORD) { 110 | // 检查是否已有token配置 111 | let tokenConfig = await getTokenConfig(env); 112 | 113 | // 如果没有token配置,创建一个默认的 114 | if (!tokenConfig) { 115 | tokenConfig = { 116 | token: generateToken(), 117 | expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 默认30天 118 | createdAt: new Date().toISOString(), 119 | lastUsed: null 120 | }; 121 | await env.IP_STORAGE.put('token_config', JSON.stringify(tokenConfig)); 122 | } 123 | 124 | // 创建会话 125 | const sessionId = generateToken(); 126 | await env.IP_STORAGE.put(`session_${sessionId}`, JSON.stringify({ 127 | loggedIn: true, 128 | createdAt: new Date().toISOString() 129 | }), { expirationTtl: 86400 }); // 24小时过期 130 | 131 | return jsonResponse({ 132 | success: true, 133 | sessionId: sessionId, 134 | tokenConfig: tokenConfig, 135 | message: '登录成功' 136 | }); 137 | } else { 138 | return jsonResponse({ 139 | success: false, 140 | error: '密码错误' 141 | }, 401); 142 | } 143 | } catch (error) { 144 | return jsonResponse({ error: error.message }, 500); 145 | } 146 | } 147 | 148 | // Token管理 149 | async function handleAdminToken(request, env) { 150 | if (!await verifyAdmin(request, env)) { 151 | return jsonResponse({ error: '需要管理员权限' }, 401); 152 | } 153 | 154 | if (request.method === 'GET') { 155 | const tokenConfig = await getTokenConfig(env); 156 | return jsonResponse({ tokenConfig }); 157 | } else if (request.method === 'POST') { 158 | try { 159 | const { token, expiresDays, neverExpire } = await request.json(); 160 | 161 | if (!token) { 162 | return jsonResponse({ error: 'Token不能为空' }, 400); 163 | } 164 | 165 | let expiresDate; 166 | if (neverExpire) { 167 | // 设置一个很远的未来日期作为永不过期 168 | expiresDate = new Date(Date.now() + 100 * 365 * 24 * 60 * 60 * 1000).toISOString(); // 100年 169 | } else { 170 | if (!expiresDays) { 171 | return jsonResponse({ error: '过期时间不能为空' }, 400); 172 | } 173 | if (expiresDays < 1 || expiresDays > 365) { 174 | return jsonResponse({ error: '过期时间必须在1-365天之间' }, 400); 175 | } 176 | expiresDate = new Date(Date.now() + expiresDays * 24 * 60 * 60 * 1000).toISOString(); 177 | } 178 | 179 | const tokenConfig = { 180 | token: token.trim(), 181 | expires: expiresDate, 182 | createdAt: new Date().toISOString(), 183 | lastUsed: null, 184 | neverExpire: neverExpire || false 185 | }; 186 | 187 | await env.IP_STORAGE.put('token_config', JSON.stringify(tokenConfig)); 188 | 189 | return jsonResponse({ 190 | success: true, 191 | tokenConfig, 192 | message: 'Token更新成功' 193 | }); 194 | } catch (error) { 195 | return jsonResponse({ error: error.message }, 500); 196 | } 197 | } else { 198 | return jsonResponse({ error: 'Method not allowed' }, 405); 199 | } 200 | } 201 | 202 | // 检查管理员状态 203 | async function handleAdminStatus(env) { 204 | try { 205 | const tokenConfig = await getTokenConfig(env); 206 | return jsonResponse({ 207 | hasAdminPassword: !!env.ADMIN_PASSWORD, 208 | hasToken: !!tokenConfig, 209 | tokenConfig: tokenConfig 210 | }); 211 | } catch (error) { 212 | return jsonResponse({ error: error.message }, 500); 213 | } 214 | } 215 | 216 | // 管理员登出 217 | async function handleAdminLogout(env) { 218 | try { 219 | // 这里可以添加会话清理逻辑 220 | return jsonResponse({ 221 | success: true, 222 | message: '已退出登录' 223 | }); 224 | } catch (error) { 225 | return jsonResponse({ error: error.message }, 500); 226 | } 227 | } 228 | 229 | // 获取Token配置 230 | async function getTokenConfig(env) { 231 | try { 232 | const config = await env.IP_STORAGE.get('token_config'); 233 | return config ? JSON.parse(config) : null; 234 | } catch (error) { 235 | return null; 236 | } 237 | } 238 | 239 | // 生成随机Token 240 | function generateToken() { 241 | const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 242 | let result = ''; 243 | for (let i = 0; i < 32; i++) { 244 | result += chars.charAt(Math.floor(Math.random() * chars.length)); 245 | } 246 | return result; 247 | } 248 | 249 | // 验证管理员权限 250 | async function verifyAdmin(request, env) { 251 | if (!env.ADMIN_PASSWORD) { 252 | return true; // 如果没有设置管理员密码,则允许所有访问 253 | } 254 | 255 | try { 256 | // 检查会话 257 | const authHeader = request.headers.get('Authorization'); 258 | if (authHeader && authHeader.startsWith('Bearer ')) { 259 | const sessionId = authHeader.slice(7); 260 | const session = await env.IP_STORAGE.get(`session_${sessionId}`); 261 | if (session) { 262 | return true; 263 | } 264 | } 265 | 266 | // 检查URL参数中的会话 267 | const url = new URL(request.url); 268 | const sessionId = url.searchParams.get('session'); 269 | if (sessionId) { 270 | const session = await env.IP_STORAGE.get(`session_${sessionId}`); 271 | if (session) { 272 | return true; 273 | } 274 | } 275 | 276 | // 检查Token 277 | const tokenConfig = await getTokenConfig(env); 278 | if (tokenConfig) { 279 | // 检查Token是否过期(永不过期的token跳过此检查) 280 | if (!tokenConfig.neverExpire && new Date(tokenConfig.expires) < new Date()) { 281 | return false; 282 | } 283 | 284 | // 检查URL参数中的token 285 | const urlToken = url.searchParams.get('token'); 286 | if (urlToken === tokenConfig.token) { 287 | // 更新最后使用时间 288 | tokenConfig.lastUsed = new Date().toISOString(); 289 | await env.IP_STORAGE.put('token_config', JSON.stringify(tokenConfig)); 290 | return true; 291 | } 292 | 293 | // 检查Authorization头中的token 294 | if (authHeader && authHeader.startsWith('Token ')) { 295 | const requestToken = authHeader.slice(6); 296 | if (requestToken === tokenConfig.token) { 297 | tokenConfig.lastUsed = new Date().toISOString(); 298 | await env.IP_STORAGE.put('token_config', JSON.stringify(tokenConfig)); 299 | return true; 300 | } 301 | } 302 | } 303 | 304 | return false; 305 | } catch (error) { 306 | return false; 307 | } 308 | } 309 | 310 | // 为URL添加认证参数 311 | function addAuthToUrl(url, sessionId, tokenConfig) { 312 | if (!sessionId && !tokenConfig) return url; 313 | 314 | const separator = url.includes('?') ? '&' : '?'; 315 | 316 | if (sessionId) { 317 | return `${url}${separator}session=${encodeURIComponent(sessionId)}`; 318 | } else if (tokenConfig) { 319 | return `${url}${separator}token=${encodeURIComponent(tokenConfig.token)}`; 320 | } 321 | 322 | return url; 323 | } 324 | 325 | // 提供HTML页面 326 | async function serveHTML(env, request) { 327 | const data = await getStoredIPs(env); 328 | 329 | // 获取测速后的IP数据 330 | const speedData = await getStoredSpeedIPs(env); 331 | const fastIPs = speedData.fastIPs || []; 332 | 333 | // 检查管理员状态 334 | const isLoggedIn = await verifyAdmin(request, env); 335 | const hasAdminPassword = !!env.ADMIN_PASSWORD; 336 | const tokenConfig = await getTokenConfig(env); 337 | 338 | // 获取会话ID 339 | let sessionId = null; 340 | if (isLoggedIn) { 341 | const url = new URL(request.url); 342 | sessionId = url.searchParams.get('session'); 343 | if (!sessionId) { 344 | const authHeader = request.headers.get('Authorization'); 345 | if (authHeader && authHeader.startsWith('Bearer ')) { 346 | sessionId = authHeader.slice(7); 347 | } 348 | } 349 | } 350 | 351 | const html = ` 352 | 353 | 354 | 355 | 356 | Cloudflare IP 收集器 357 | 1088 | 1089 | 1090 | 1091 |
1092 |
1093 | ${isLoggedIn ? '🔐 管理员' : '🔓 点击登录'} 1094 | ${isLoggedIn ? '' : ''} 1095 |
1096 | ${isLoggedIn ? ` 1097 | 1100 | ` : ''} 1101 |
1102 | 1103 |
1104 | 1105 |
1106 |
1107 |

Cloudflare 优选IP 收集器

1108 |

自动定时拉取IP并测速

1109 |
1110 | 1127 |
1128 | 1129 | 1130 |
1131 |

📊 系统状态

1132 |
1133 |
1134 |
${data.count || 0}
1135 |
IP 地址数量
1136 |
1137 |
1138 |
${data.lastUpdated ? '已更新' : '未更新'}
1139 |
最后更新
1140 |
1141 |
1142 |
${data.lastUpdated ? new Date(data.lastUpdated).toLocaleTimeString() : '从未更新'}
1143 |
更新时间
1144 |
1145 |
1146 |
${fastIPs.length}
1147 |
优质 IP 数量
1148 |
1149 |
1150 | 1151 |
1152 | 1155 | 1156 | 1157 | 1166 | 1167 | 1168 | 1177 | 1178 | 1181 | 1184 | 1187 | 1188 | 1191 |
1192 | 1193 |
1194 |
1195 |

正在从多个来源收集 IP 地址,请稍候...

1196 |
1197 | 1198 |
1199 | 1200 | 1201 | ${isLoggedIn ? ` 1202 |
1203 |

🔑 API Token 管理

1204 | ${tokenConfig ? ` 1205 |
1206 |

当前 Token:

1207 |
${tokenConfig.token}
1208 |

过期时间: ${tokenConfig.neverExpire ? '永不过期' : new Date(tokenConfig.expires).toLocaleString()}

1209 |

创建时间: ${new Date(tokenConfig.createdAt).toLocaleString()}

1210 | ${tokenConfig.lastUsed ? `

最后使用: ${new Date(tokenConfig.lastUsed).toLocaleString()}

` : ''} 1211 |
1212 | ` : '

暂无Token配置,请点击下方按钮创建Token。

'} 1213 |
1214 | 1217 | ${tokenConfig ? ` 1218 | 1221 | 1224 | ` : ''} 1225 |
1226 |
1227 | ` : ''} 1228 |
1229 | 1230 | 1231 |
1232 |
1233 |

⚡ 优质 IP 列表

1234 |
1235 | 1238 |
1239 |
1240 | 1241 |
1242 |
1243 |
1244 |
准备测速...
1245 | 1246 |
1247 | ${fastIPs.length > 0 ? 1248 | fastIPs.map(item => { 1249 | const ip = item.ip; 1250 | const latency = item.latency; 1251 | const speedClass = latency < 200 ? 'speed-fast' : latency < 500 ? 'speed-medium' : 'speed-slow'; 1252 | return ` 1253 |
1254 |
1255 | ${ip} 1256 | ${latency}ms 1257 |
1258 |
1259 | 1260 |
1261 |
1262 | `}).join('') : 1263 | '

暂无优质 IP 地址数据,请点击更新按钮获取

' 1264 | } 1265 |
1266 |
1267 | 1268 | 1269 |
1270 |

🌍 数据来源状态

1271 |
1272 | ${data.sources ? data.sources.map(source => ` 1273 |
1274 | ${source.name}: 1275 | ${source.status === 'success' ? 1276 | `成功获取 ${source.count} 个IP` : 1277 | `失败: ${source.error}` 1278 | } 1279 |
1280 | `).join('') : '

暂无数据来源信息

'} 1281 |
1282 |
1283 | 1284 | 1285 | 1288 |
1289 | 1290 | 1291 | 1310 | 1311 | 1312 |
1313 | 1327 |
1328 | 1329 | 1330 | 1354 | 1355 | 1951 | 1952 | `; 1953 | 1954 | return new Response(html, { 1955 | headers: { 1956 | 'Content-Type': 'text/html; charset=utf-8', 1957 | } 1958 | }); 1959 | } 1960 | 1961 | // 处理优质IP列表获取(JSON格式) 1962 | async function handleGetFastIPs(env, request) { 1963 | if (!await verifyAdmin(request, env)) { 1964 | return jsonResponse({ error: '需要管理员权限' }, 401); 1965 | } 1966 | 1967 | const data = await getStoredSpeedIPs(env); 1968 | return jsonResponse(data); 1969 | } 1970 | 1971 | // 处理优质IP列表获取(文本格式,IP#实际的延迟ms格式) 1972 | async function handleGetFastIPsText(env, request) { 1973 | if (!await verifyAdmin(request, env)) { 1974 | return jsonResponse({ error: '需要管理员权限' }, 401); 1975 | } 1976 | 1977 | const data = await getStoredSpeedIPs(env); 1978 | const fastIPs = data.fastIPs || []; 1979 | 1980 | // 格式化为 IP#实际的延迟ms 1981 | const ipList = fastIPs.map(item => `${item.ip}#${item.latency}ms`).join('\n'); 1982 | 1983 | return new Response(ipList, { 1984 | headers: { 1985 | 'Content-Type': 'text/plain; charset=utf-8', 1986 | 'Content-Disposition': 'inline; filename="cloudflare_fast_ips.txt"', 1987 | 'Access-Control-Allow-Origin': '*' 1988 | } 1989 | }); 1990 | } 1991 | 1992 | // 处理 ITDog 数据获取 1993 | async function handleItdogData(env, request) { 1994 | if (!await verifyAdmin(request, env)) { 1995 | return jsonResponse({ error: '需要管理员权限' }, 401); 1996 | } 1997 | 1998 | const data = await getStoredIPs(env); 1999 | return jsonResponse({ 2000 | ips: data.ips || [], 2001 | count: data.count || 0 2002 | }); 2003 | } 2004 | 2005 | // 处理测速请求 2006 | async function handleSpeedTest(request, env) { 2007 | const url = new URL(request.url); 2008 | const ip = url.searchParams.get('ip'); 2009 | 2010 | if (!ip) { 2011 | return jsonResponse({ error: 'IP parameter is required' }, 400); 2012 | } 2013 | 2014 | try { 2015 | // 使用 Cloudflare 的测速域名 2016 | const testUrl = `https://speed.cloudflare.com/__down?bytes=1000`; 2017 | 2018 | // 设置自定义 Host 头来指向特定 IP 2019 | const response = await fetch(testUrl, { 2020 | headers: { 2021 | 'Host': 'speed.cloudflare.com' 2022 | }, 2023 | cf: { 2024 | // 使用 resolveOverride 来指定 IP 2025 | resolveOverride: ip 2026 | } 2027 | }); 2028 | 2029 | if (!response.ok) { 2030 | throw new Error(`HTTP ${response.status}: ${response.statusText}`); 2031 | } 2032 | 2033 | // 读取响应以确保连接完成 2034 | await response.text(); 2035 | 2036 | return jsonResponse({ 2037 | success: true, 2038 | ip: ip, 2039 | time: new Date().toISOString() 2040 | }); 2041 | 2042 | } catch (error) { 2043 | console.error(`Speed test failed for IP ${ip}:`, error); 2044 | return jsonResponse({ 2045 | success: false, 2046 | ip: ip, 2047 | error: error.message, 2048 | time: new Date().toISOString() 2049 | }, 500); 2050 | } 2051 | } 2052 | 2053 | // 处理手动更新 2054 | async function handleUpdate(env, request) { 2055 | if (!await verifyAdmin(request, env)) { 2056 | return jsonResponse({ error: '需要管理员权限' }, 401); 2057 | } 2058 | 2059 | try { 2060 | // 再次检查 KV 绑定 2061 | if (!env.IP_STORAGE) { 2062 | throw new Error('KV namespace IP_STORAGE is not bound. Please check your Worker settings.'); 2063 | } 2064 | 2065 | const startTime = Date.now(); 2066 | const { uniqueIPs, results } = await updateAllIPs(env); 2067 | const duration = Date.now() - startTime; 2068 | 2069 | // 存储到 KV 2070 | await env.IP_STORAGE.put('cloudflare_ips', JSON.stringify({ 2071 | ips: uniqueIPs, 2072 | lastUpdated: new Date().toISOString(), 2073 | count: uniqueIPs.length, 2074 | sources: results 2075 | })); 2076 | 2077 | // 自动触发测速并存储优质IP 2078 | await autoSpeedTestAndStore(env, uniqueIPs); 2079 | 2080 | return jsonResponse({ 2081 | success: true, 2082 | message: 'IPs collected and speed test completed successfully', 2083 | duration: `${duration}ms`, 2084 | totalIPs: uniqueIPs.length, 2085 | timestamp: new Date().toISOString(), 2086 | results: results 2087 | }); 2088 | } catch (error) { 2089 | console.error('Update error:', error); 2090 | return jsonResponse({ 2091 | success: false, 2092 | error: error.message 2093 | }, 500); 2094 | } 2095 | } 2096 | 2097 | // 自动测速并存储优质IP - 优化后的逻辑 2098 | async function autoSpeedTestAndStore(env, ips) { 2099 | if (!ips || ips.length === 0) return; 2100 | 2101 | const speedResults = []; 2102 | const BATCH_SIZE = 5; // 控制并发数 2103 | 2104 | // 对所有IP进行测速,但限制最大数量避免超时 2105 | const ipsToTest = ips.slice(0, AUTO_TEST_MAX_IPS); 2106 | 2107 | console.log(`Starting auto speed test for ${ipsToTest.length} IPs (out of ${ips.length} total)...`); 2108 | 2109 | for (let i = 0; i < ipsToTest.length; i += BATCH_SIZE) { 2110 | const batch = ipsToTest.slice(i, i + BATCH_SIZE); 2111 | const batchPromises = batch.map(ip => testIPSpeed(ip)); 2112 | 2113 | const batchResults = await Promise.allSettled(batchPromises); 2114 | 2115 | for (let j = 0; j < batchResults.length; j++) { 2116 | const result = batchResults[j]; 2117 | const ip = batch[j]; 2118 | 2119 | if (result.status === 'fulfilled') { 2120 | const speedData = result.value; 2121 | if (speedData.success && speedData.latency) { 2122 | speedResults.push({ 2123 | ip: ip, 2124 | latency: Math.round(speedData.latency) // 确保延迟是整数 2125 | }); 2126 | } 2127 | } 2128 | } 2129 | 2130 | // 批次间延迟 2131 | if (i + BATCH_SIZE < ipsToTest.length) { 2132 | await new Promise(resolve => setTimeout(resolve, 500)); 2133 | } 2134 | } 2135 | 2136 | // 按延迟排序,取前FAST_IP_COUNT个最快的IP 2137 | speedResults.sort((a, b) => a.latency - b.latency); 2138 | const fastIPs = speedResults.slice(0, FAST_IP_COUNT); 2139 | 2140 | console.log(`Speed test results: ${speedResults.length} IPs tested successfully`); 2141 | console.log(`Fastest IP: ${fastIPs[0]?.ip} (${fastIPs[0]?.latency}ms)`); 2142 | console.log(`Slowest fast IP: ${fastIPs[fastIPs.length-1]?.ip} (${fastIPs[fastIPs.length-1]?.latency}ms)`); 2143 | 2144 | // 存储优质IP 2145 | await env.IP_STORAGE.put('cloudflare_fast_ips', JSON.stringify({ 2146 | fastIPs: fastIPs, 2147 | lastTested: new Date().toISOString(), 2148 | count: fastIPs.length, 2149 | testedCount: speedResults.length, 2150 | totalIPs: ips.length 2151 | })); 2152 | 2153 | console.log(`Auto speed test completed. Found ${fastIPs.length} fast IPs out of ${speedResults.length} tested.`); 2154 | } 2155 | 2156 | // 测试单个IP的速度 2157 | async function testIPSpeed(ip) { 2158 | try { 2159 | const startTime = Date.now(); 2160 | const testUrl = `https://speed.cloudflare.com/__down?bytes=1000`; 2161 | 2162 | const response = await fetch(testUrl, { 2163 | headers: { 2164 | 'Host': 'speed.cloudflare.com' 2165 | }, 2166 | cf: { 2167 | resolveOverride: ip 2168 | }, 2169 | // 设置较短的超时时间 2170 | signal: AbortSignal.timeout(5000) 2171 | }); 2172 | 2173 | if (!response.ok) { 2174 | throw new Error(`HTTP ${response.status}: ${response.statusText}`); 2175 | } 2176 | 2177 | await response.text(); 2178 | const endTime = Date.now(); 2179 | const latency = endTime - startTime; 2180 | 2181 | return { 2182 | success: true, 2183 | ip: ip, 2184 | latency: latency 2185 | }; 2186 | 2187 | } catch (error) { 2188 | return { 2189 | success: false, 2190 | ip: ip, 2191 | error: error.message 2192 | }; 2193 | } 2194 | } 2195 | 2196 | // 处理获取IP列表 - 纯文本格式 2197 | async function handleGetIPs(env, request) { 2198 | if (!await verifyAdmin(request, env)) { 2199 | return jsonResponse({ error: '需要管理员权限' }, 401); 2200 | } 2201 | 2202 | const data = await getStoredIPs(env); 2203 | return new Response(data.ips.join('\n'), { 2204 | headers: { 2205 | 'Content-Type': 'text/plain; charset=utf-8', 2206 | 'Content-Disposition': 'inline; filename="cloudflare_ips.txt"', 2207 | 'Access-Control-Allow-Origin': '*' 2208 | } 2209 | }); 2210 | } 2211 | 2212 | // 处理获取原始数据 2213 | async function handleRawIPs(env, request) { 2214 | if (!await verifyAdmin(request, env)) { 2215 | return jsonResponse({ error: '需要管理员权限' }, 401); 2216 | } 2217 | 2218 | const data = await getStoredIPs(env); 2219 | return jsonResponse(data); 2220 | } 2221 | 2222 | // 主要的IP收集逻辑 2223 | async function updateAllIPs(env) { 2224 | const urls = [ 2225 | 'https://ip.164746.xyz', 2226 | 'https://ip.haogege.xyz/', 2227 | 'https://stock.hostmonit.com/CloudFlareYes', 2228 | 'https://api.uouin.com/cloudflare.html', 2229 | 'https://addressesapi.090227.xyz/CloudFlareYes', 2230 | 'https://addressesapi.090227.xyz/ip.164746.xyz', 2231 | 'https://www.wetest.vip/page/cloudflare/address_v4.html' 2232 | ]; 2233 | 2234 | const uniqueIPs = new Set(); 2235 | const results = []; 2236 | 2237 | // 使用与Python脚本相同的正则表达式 2238 | const ipPattern = /\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/gi; 2239 | 2240 | // 批量处理URL,控制并发数 2241 | const BATCH_SIZE = 3; 2242 | for (let i = 0; i < urls.length; i += BATCH_SIZE) { 2243 | const batch = urls.slice(i, i + BATCH_SIZE); 2244 | const batchPromises = batch.map(url => fetchURLWithTimeout(url, 8000)); 2245 | 2246 | const batchResults = await Promise.allSettled(batchPromises); 2247 | 2248 | for (let j = 0; j < batchResults.length; j++) { 2249 | const result = batchResults[j]; 2250 | const url = batch[j]; 2251 | const sourceName = getSourceName(url); 2252 | 2253 | if (result.status === 'fulfilled') { 2254 | const content = result.value; 2255 | const ipMatches = content.match(ipPattern) || []; 2256 | 2257 | // 添加到集合中(自动去重) 2258 | ipMatches.forEach(ip => { 2259 | if (isValidIPv4(ip)) { 2260 | uniqueIPs.add(ip); 2261 | } 2262 | }); 2263 | 2264 | results.push({ 2265 | name: sourceName, 2266 | status: 'success', 2267 | count: ipMatches.length, 2268 | error: null 2269 | }); 2270 | 2271 | console.log(`Successfully collected ${ipMatches.length} IPs from ${sourceName}`); 2272 | } else { 2273 | console.error(`Failed to fetch ${sourceName}:`, result.reason); 2274 | results.push({ 2275 | name: sourceName, 2276 | status: 'error', 2277 | count: 0, 2278 | error: result.reason.message 2279 | }); 2280 | } 2281 | } 2282 | 2283 | // 批次间延迟 2284 | if (i + BATCH_SIZE < urls.length) { 2285 | await new Promise(resolve => setTimeout(resolve, 1000)); 2286 | } 2287 | } 2288 | 2289 | // 按IP地址的数字顺序排序(与Python脚本相同) 2290 | const sortedIPs = Array.from(uniqueIPs).sort((a, b) => { 2291 | const aParts = a.split('.').map(part => parseInt(part, 10)); 2292 | const bParts = b.split('.').map(part => parseInt(part, 10)); 2293 | 2294 | for (let i = 0; i < 4; i++) { 2295 | if (aParts[i] !== bParts[i]) { 2296 | return aParts[i] - bParts[i]; 2297 | } 2298 | } 2299 | return 0; 2300 | }); 2301 | 2302 | return { 2303 | uniqueIPs: sortedIPs, 2304 | results: results 2305 | }; 2306 | } 2307 | 2308 | // 获取URL的友好名称 2309 | function getSourceName(url) { 2310 | try { 2311 | const urlObj = new URL(url); 2312 | return urlObj.hostname + (urlObj.pathname !== '/' ? urlObj.pathname : ''); 2313 | } catch (e) { 2314 | return url; 2315 | } 2316 | } 2317 | 2318 | // 带超时的fetch 2319 | async function fetchURLWithTimeout(url, timeout = 8000) { 2320 | const controller = new AbortController(); 2321 | const timeoutId = setTimeout(() => controller.abort(), timeout); 2322 | 2323 | try { 2324 | const response = await fetch(url, { 2325 | signal: controller.signal, 2326 | headers: { 2327 | 'User-Agent': 'Mozilla/5.0 (compatible; Cloudflare-IP-Collector/1.0)', 2328 | 'Accept': 'text/html,application/json,text/plain,*/*' 2329 | } 2330 | }); 2331 | 2332 | if (!response.ok) { 2333 | throw new Error(`HTTP ${response.status}: ${response.statusText}`); 2334 | } 2335 | 2336 | return await response.text(); 2337 | } finally { 2338 | clearTimeout(timeoutId); 2339 | } 2340 | } 2341 | 2342 | // 从 KV 获取存储的 IPs 2343 | async function getStoredIPs(env) { 2344 | try { 2345 | if (!env.IP_STORAGE) { 2346 | console.error('KV namespace IP_STORAGE is not bound'); 2347 | return getDefaultData(); 2348 | } 2349 | 2350 | const data = await env.IP_STORAGE.get('cloudflare_ips'); 2351 | if (data) { 2352 | return JSON.parse(data); 2353 | } 2354 | } catch (error) { 2355 | console.error('Error reading from KV:', error); 2356 | } 2357 | 2358 | return getDefaultData(); 2359 | } 2360 | 2361 | // 从 KV 获取存储的测速IPs 2362 | async function getStoredSpeedIPs(env) { 2363 | try { 2364 | if (!env.IP_STORAGE) { 2365 | console.error('KV namespace IP_STORAGE is not bound'); 2366 | return getDefaultSpeedData(); 2367 | } 2368 | 2369 | const data = await env.IP_STORAGE.get('cloudflare_fast_ips'); 2370 | if (data) { 2371 | return JSON.parse(data); 2372 | } 2373 | } catch (error) { 2374 | console.error('Error reading speed IPs from KV:', error); 2375 | } 2376 | 2377 | return getDefaultSpeedData(); 2378 | } 2379 | 2380 | // 默认数据 2381 | function getDefaultData() { 2382 | return { 2383 | ips: [], 2384 | lastUpdated: null, 2385 | count: 0, 2386 | sources: [] 2387 | }; 2388 | } 2389 | 2390 | // 默认测速数据 2391 | function getDefaultSpeedData() { 2392 | return { 2393 | fastIPs: [], 2394 | lastTested: null, 2395 | count: 0 2396 | }; 2397 | } 2398 | 2399 | // IPv4地址验证 2400 | function isValidIPv4(ip) { 2401 | const parts = ip.split('.'); 2402 | if (parts.length !== 4) return false; 2403 | 2404 | for (const part of parts) { 2405 | const num = parseInt(part, 10); 2406 | if (isNaN(num) || num < 0 || num > 255) return false; 2407 | // 排除私有IP段 2408 | if (part.startsWith('0') && part.length > 1) return false; 2409 | } 2410 | 2411 | // 排除私有地址 2412 | if (ip.startsWith('10.') || 2413 | ip.startsWith('192.168.') || 2414 | (ip.startsWith('172.') && parseInt(parts[1]) >= 16 && parseInt(parts[1]) <= 31) || 2415 | ip.startsWith('127.') || 2416 | ip.startsWith('169.254.') || 2417 | ip === '255.255.255.255') { 2418 | return false; 2419 | } 2420 | 2421 | return true; 2422 | } 2423 | 2424 | // 工具函数 2425 | function jsonResponse(data, status = 200) { 2426 | return new Response(JSON.stringify(data, null, 2), { 2427 | status, 2428 | headers: { 2429 | 'Content-Type': 'application/json', 2430 | 'Access-Control-Allow-Origin': '*' 2431 | } 2432 | }); 2433 | } 2434 | 2435 | function handleCORS() { 2436 | return new Response(null, { 2437 | headers: { 2438 | 'Access-Control-Allow-Origin': '*', 2439 | 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 2440 | 'Access-Control-Allow-Headers': 'Content-Type' 2441 | } 2442 | }); 2443 | } 2444 | --------------------------------------------------------------------------------