新增在线优选IP功能,支持动态加载IP列表和用户界面优化

This commit is contained in:
cmliu
2025-06-08 19:39:53 +08:00
parent f4fd76a700
commit d2aebefd33

View File

@@ -2178,6 +2178,17 @@ async function KV(request, env, txt = 'ADD.txt') {
.back-btn:hover {
background: #555;
}
.bestip-btn {
background: #2196F3;
padding: 6px 15px;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.bestip-btn:hover {
background: #1976D2;
}
.save-status {
color: #666;
}
@@ -2204,6 +2215,7 @@ async function KV(request, env, txt = 'ADD.txt') {
id="content">${content}</textarea>
<div class="save-container">
<button class="back-btn" onclick="goBack()">返回配置页</button>
<button class="bestip-btn" onclick="goBestIP()">在线优选IP</button>
<button class="save-btn" onclick="saveContent(this)">保存</button>
<span class="save-status" id="saveStatus"></span>
</div>
@@ -2225,6 +2237,12 @@ async function KV(request, env, txt = 'ADD.txt') {
window.location.href = parentUrl;
}
function goBestIP() {
const currentUrl = window.location.href;
const parentUrl = currentUrl.substring(0, currentUrl.lastIndexOf('/'));
window.location.href = parentUrl + '/bestip';
}
function replaceFullwidthColon() {
const text = textarea.value;
textarea.value = text.replace(//g, ':');
@@ -2549,12 +2567,30 @@ async function bestIP(request, env, txt = 'ADD.txt') {
const country = request.cf?.country || 'CN';
const url = new URL(request.url);
async function GetCFIPs() {
async function GetCFIPs(ipSource = 'official') {
try {
// 首先尝试第一个URL
let response = await fetch('https://raw.githubusercontent.com/ipverse/asn-ip/master/as/13335/ipv4-aggregated.txt');
if (!response.ok) {
// 如果失败尝试第二个URL
let response;
if (ipSource === 'as13335') {
// AS13335列表
response = await fetch('https://raw.githubusercontent.com/ipverse/asn-ip/master/as/13335/ipv4-aggregated.txt');
} else if (ipSource === 'as209242') {
// AS209242列表
response = await fetch('https://raw.githubusercontent.com/ipverse/asn-ip/master/as/209242/ipv4-aggregated.txt');
} else if (ipSource === 'cm') {
// CM整理列表
response = await fetch('https://raw.githubusercontent.com/cmliu/cmliu/main/CF-CIDR.txt');
} else if (ipSource === 'baipiao') {
// 反代IP列表 (直接IP非CIDR)
response = await fetch('https://raw.githubusercontent.com/cmliu/ACL4SSR/main/baipiao.txt');
const text = response.ok ? await response.text() : '';
// 直接返回IP列表不进行CIDR生成
const ips = text.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'));
console.log(`反代IP列表获取到${ips.length}个IP`);
return ips;
} else {
// CF官方列表 (默认)
response = await fetch('https://www.cloudflare.com/ips-v4/');
}
@@ -2772,12 +2808,13 @@ async function bestIP(request, env, txt = 'ADD.txt') {
}
}
const cfIPs = await GetCFIPs();
// 移除初始IP加载改为在前端动态加载
const cfIPs = []; // 初始为空数组
// 判断是否为中国用户
const isChina = country === 'CN';
const countryDisplayClass = isChina ? '' : 'proxy-warning';
const countryDisplayText = isChina ? country : `${country} ⚠️`;
const countryDisplayText = isChina ? `${country}` : `${country} ⚠️`;
const html = `
<!DOCTYPE html>
@@ -2808,6 +2845,19 @@ async function bestIP(request, env, txt = 'ADD.txt') {
border-radius: 5px;
margin: 20px 0;
}
.test-info {
margin-top: 15px;
padding: 12px;
background-color: #f3e5f5;
border: 1px solid #ce93d8;
border-radius: 6px;
color: #4a148c;
}
.test-info p {
margin: 0;
font-size: 14px;
line-height: 1.5;
}
.proxy-warning {
color: #d32f2f !important;
font-weight: bold !important;
@@ -2919,6 +2969,22 @@ async function bestIP(request, env, txt = 'ADD.txt') {
.append-button:not(:disabled):hover {
background-color: #F57C00;
}
.edit-button {
background-color: #9C27B0;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
cursor: pointer;
border: none;
border-radius: 4px;
transition: background-color 0.3s;
}
.edit-button:hover {
background-color: #7B1FA2;
}
.back-button {
background-color: #607D8B;
color: white;
@@ -2986,16 +3052,6 @@ async function bestIP(request, env, txt = 'ADD.txt') {
<body>
<h1>在线优选IP</h1>
<div class="stats">
<h2>统计信息</h2>
<p><strong>您的国家:</strong><span class="${countryDisplayClass}">${countryDisplayText}</span></p>
<p><strong>获取到的IP总数</strong>${cfIPs.length} 个</p>
<p><strong>测试进度:</strong><span id="progress-text">未开始</span></p>
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
</div>
${!isChina ? `
<div class="warning-notice">
<h3>🚨 代理检测警告</h3>
@@ -3009,12 +3065,34 @@ async function bestIP(request, env, txt = 'ADD.txt') {
<p><strong>建议操作:</strong>请关闭所有代理软件VPN、科学上网工具等确保处于直连网络环境后重新访问本页面。</p>
</div>
` : ''}
<div class="stats">
<h2>统计信息</h2>
<p><strong>您的国家:</strong><span class="${countryDisplayClass}">${countryDisplayText}</span></p>
<p><strong>获取到的IP总数</strong><span id="ip-count">点击开始测试后加载</span></p>
<p><strong>测试进度:</strong><span id="progress-text">未开始</span></p>
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div class="test-info">
<p><strong>📊 测试说明:</strong>当前优选方式仅进行网络延迟测试主要评估连接响应速度并未包含带宽速度测试。延迟测试可快速筛选出响应最快的IP节点适合日常使用场景的初步优选。</p>
</div>
</div>
<div class="test-controls">
<div class="port-selector">
<label for="port-select">选择端口</label>
<label for="ip-source-select">IP库</label>
<select id="ip-source-select">
<option value="official">CF官方列表</option>
<option value="cm">CM整理列表</option>
<option value="as13335">AS13335列表</option>
<option value="as209242">AS209242列表</option>
<option value="baipiao">反代IP列表</option>
</select>
<label for="port-select" style="margin-left: 20px;">端口:</label>
<select id="port-select">
<option value="443" selected>443</option>
<option value="443">443</option>
<option value="2053">2053</option>
<option value="2083">2083</option>
<option value="2087">2087</option>
@@ -3026,6 +3104,7 @@ async function bestIP(request, env, txt = 'ADD.txt') {
<button class="test-button" id="test-btn" onclick="startTest()">开始延迟测试</button>
<button class="save-button" id="save-btn" onclick="saveIPs()" disabled>覆盖保存优选IP</button>
<button class="append-button" id="append-btn" onclick="appendIPs()" disabled>追加保存优选IP</button>
<button class="edit-button" id="edit-btn" onclick="goEdit()">编辑优选列表</button>
<button class="back-button" id="back-btn" onclick="goBack()">返回配置页</button>
</div>
<div class="save-warning">
@@ -3036,14 +3115,56 @@ async function bestIP(request, env, txt = 'ADD.txt') {
<h2>IP列表 <span id="result-count"></span></h2>
<div class="ip-list" id="ip-list">
${cfIPs.map(ip => `<div class="ip-item">${ip}</div>`).join('')}
<div class="ip-item">请选择端口和IP库然后点击"开始延迟测试"加载IP列表</div>
</div>
<script>
const originalIPs = ${JSON.stringify(cfIPs)};
let originalIPs = []; // 改为动态加载
let testResults = [];
let displayedResults = []; // 新增:存储当前显示的结果
// 新增:本地存储管理
const StorageKeys = {
PORT: 'cf-ip-test-port',
IP_SOURCE: 'cf-ip-test-source'
};
// 初始化页面设置
function initializeSettings() {
const portSelect = document.getElementById('port-select');
const ipSourceSelect = document.getElementById('ip-source-select');
// 从本地存储读取上次的选择
const savedPort = localStorage.getItem(StorageKeys.PORT);
const savedIPSource = localStorage.getItem(StorageKeys.IP_SOURCE);
// 恢复端口选择
if (savedPort && portSelect.querySelector(\`option[value="\${savedPort}"]\`)) {
portSelect.value = savedPort;
} else {
portSelect.value = '8443'; // 默认值
}
// 恢复IP库选择
if (savedIPSource && ipSourceSelect.querySelector(\`option[value="\${savedIPSource}"]\`)) {
ipSourceSelect.value = savedIPSource;
} else {
ipSourceSelect.value = 'official'; // 默认值改为CF官方列表
}
// 添加事件监听器保存选择
portSelect.addEventListener('change', function() {
localStorage.setItem(StorageKeys.PORT, this.value);
});
ipSourceSelect.addEventListener('change', function() {
localStorage.setItem(StorageKeys.IP_SOURCE, this.value);
});
}
// 页面加载完成后初始化设置
document.addEventListener('DOMContentLoaded', initializeSettings);
function showMessage(text, type = 'success') {
const messageDiv = document.getElementById('message');
messageDiv.textContent = text;
@@ -3069,24 +3190,32 @@ async function bestIP(request, env, txt = 'ADD.txt') {
const testBtn = document.getElementById('test-btn');
const saveBtn = document.getElementById('save-btn');
const appendBtn = document.getElementById('append-btn');
const editBtn = document.getElementById('edit-btn');
const backBtn = document.getElementById('back-btn');
const portSelect = document.getElementById('port-select');
const ipSourceSelect = document.getElementById('ip-source-select');
testBtn.disabled = true;
saveBtn.disabled = true;
appendBtn.disabled = true;
editBtn.disabled = true;
backBtn.disabled = true;
portSelect.disabled = true;
ipSourceSelect.disabled = true;
}
function enableButtons() {
const testBtn = document.getElementById('test-btn');
const editBtn = document.getElementById('edit-btn');
const backBtn = document.getElementById('back-btn');
const portSelect = document.getElementById('port-select');
const ipSourceSelect = document.getElementById('ip-source-select');
testBtn.disabled = false;
editBtn.disabled = false;
backBtn.disabled = false;
portSelect.disabled = false;
ipSourceSelect.disabled = false;
updateButtonStates();
}
@@ -3170,6 +3299,12 @@ async function bestIP(request, env, txt = 'ADD.txt') {
}
}
function goEdit() {
const currentUrl = window.location.href;
const parentUrl = currentUrl.substring(0, currentUrl.lastIndexOf('/'));
window.location.href = parentUrl + '/edit';
}
function goBack() {
const currentUrl = window.location.href;
const parentUrl = currentUrl.substring(0, currentUrl.lastIndexOf('/'));
@@ -3179,24 +3314,28 @@ async function bestIP(request, env, txt = 'ADD.txt') {
async function testIP(ip, port) {
const timeout = 999;
// 解析IP格式
const parsedIP = parseIPFormat(ip, port);
if (!parsedIP) {
return null;
}
// 第一次测试
const firstResult = await singleTest(ip, port, timeout);
const firstResult = await singleTest(parsedIP.host, parsedIP.port, timeout);
if (!firstResult) {
return null; // 第一次测试失败,直接返回
}
// 第一次测试成功,再进行2次测试
console.log(\`IP \${ip}:\${port} 第一次测试成功: \${firstResult.latency}ms进行额外测试...\`);
// 第一次测试成功,再进行第二次测试
console.log(\`IP \${parsedIP.host}:\${parsedIP.port} 第一次测试成功: \${firstResult.latency}ms进行第二次测试...\`);
const results = [firstResult];
// 进行第二次和第三次测试
for (let i = 2; i <= 3; i++) {
const result = await singleTest(ip, port, timeout);
if (result) {
results.push(result);
console.log(\`IP \${ip}:\${port} 第\${i}次测试: \${result.latency}ms\`);
}
// 进行第二次测试
const secondResult = await singleTest(parsedIP.host, parsedIP.port, timeout);
if (secondResult) {
results.push(secondResult);
console.log(\`IP \${parsedIP.host}:\${parsedIP.port} 第二次测试: \${secondResult.latency}ms\`);
}
// 取最低延迟
@@ -3204,21 +3343,64 @@ async function bestIP(request, env, txt = 'ADD.txt') {
current.latency < best.latency ? current : best
);
// 将延迟除以2并向下取整因为是往返时间
const displayLatency = Math.floor(bestResult.latency / 2);
const displayLatency = Math.floor(bestResult.latency);
console.log(\`IP \${ip}:\${port} 最终结果: \${displayLatency}ms (原始: \${bestResult.latency}ms, 共\${results.length}次有效测试)\`);
console.log(\`IP \${parsedIP.host}:\${parsedIP.port} 最终结果: \${displayLatency}ms (原始: \${bestResult.latency}ms, 共\${results.length}次有效测试)\`);
// 生成显示格式
const comment = parsedIP.comment || 'CF优选IP';
const display = \`\${parsedIP.host}:\${parsedIP.port}#\${comment} \${displayLatency}ms\`;
return {
ip: ip,
port: port,
ip: parsedIP.host,
port: parsedIP.port,
latency: displayLatency,
originalLatency: bestResult.latency,
testCount: results.length,
display: \`\${ip}:\${port}#CF优选IP \${displayLatency}ms\`
comment: comment,
display: display
};
}
// 新增解析IP格式的函数
function parseIPFormat(ipString, defaultPort) {
try {
let host, port, comment;
// 先处理注释部分(#之后的内容)
let mainPart = ipString;
if (ipString.includes('#')) {
const parts = ipString.split('#');
mainPart = parts[0];
comment = parts[1];
}
// 处理端口部分
if (mainPart.includes(':')) {
const parts = mainPart.split(':');
host = parts[0];
port = parseInt(parts[1]);
} else {
host = mainPart;
port = parseInt(defaultPort);
}
// 验证IP格式
if (!host || !port || isNaN(port)) {
return null;
}
return {
host: host.trim(),
port: port,
comment: comment ? comment.trim() : null
};
} catch (error) {
console.error('解析IP格式失败:', ipString, error);
return null;
}
}
async function singleTest(ip, port, timeout) {
const startTime = Date.now();
@@ -3299,26 +3481,81 @@ async function bestIP(request, env, txt = 'ADD.txt') {
async function startTest() {
const testBtn = document.getElementById('test-btn');
const portSelect = document.getElementById('port-select');
const ipSourceSelect = document.getElementById('ip-source-select');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const ipList = document.getElementById('ip-list');
const resultCount = document.getElementById('result-count');
const ipCount = document.getElementById('ip-count');
const selectedPort = portSelect.value;
const selectedIPSource = ipSourceSelect.value;
// 保存当前选择到本地存储
localStorage.setItem(StorageKeys.PORT, selectedPort);
localStorage.setItem(StorageKeys.IP_SOURCE, selectedIPSource);
testBtn.disabled = true;
testBtn.textContent = '测试中...';
testBtn.textContent = '加载IP列表...';
portSelect.disabled = true;
ipSourceSelect.disabled = true;
testResults = [];
displayedResults = []; // 重置显示结果
ipList.innerHTML = '<div class="ip-item">测试中,请稍候...</div>';
ipList.innerHTML = '<div class="ip-item">正在加载IP列表,请稍候...</div>';
updateButtonStates(); // 更新按钮状态
// 重置进度条
progressBar.style.width = '0%';
// 根据IP库类型显示对应的加载信息
let ipSourceName = '';
switch(selectedIPSource) {
case 'official':
ipSourceName = 'CF官方';
break;
case 'cm':
ipSourceName = 'CM整理';
break;
case 'as13335':
ipSourceName = 'AS13335';
break;
case 'as209242':
ipSourceName = 'AS209242';
break;
case 'baipiao':
ipSourceName = '反代IP';
break;
default:
ipSourceName = '未知';
}
progressText.textContent = \`正在加载 \${ipSourceName} IP列表...\`;
// 加载IP列表
originalIPs = await loadIPs(selectedIPSource);
if (originalIPs.length === 0) {
ipList.innerHTML = '<div class="ip-item">加载IP列表失败请重试</div>';
ipCount.textContent = '0 个';
testBtn.disabled = false;
testBtn.textContent = '开始延迟测试';
portSelect.disabled = false;
ipSourceSelect.disabled = false;
progressText.textContent = '加载失败';
return;
}
// 更新IP数量显示
ipCount.textContent = \`\${originalIPs.length} 个\`;
// 显示加载的IP列表
ipList.innerHTML = originalIPs.map(ip => \`<div class="ip-item">\${ip}</div>\`).join('');
// 开始测试
testBtn.textContent = '测试中...';
progressText.textContent = \`开始测试端口 \${selectedPort}...\`;
// 使用32个并发线程测试
// 使用16个并发线程测试
const results = await testIPsWithConcurrency(originalIPs, selectedPort, 16);
// 按延迟排序
@@ -3330,7 +3567,27 @@ async function bestIP(request, env, txt = 'ADD.txt') {
testBtn.disabled = false;
testBtn.textContent = '重新测试';
portSelect.disabled = false;
progressText.textContent = \`完成 - 有效IP: \${testResults.length}/\${originalIPs.length} (端口: \${selectedPort})\`;
ipSourceSelect.disabled = false;
progressText.textContent = \`完成 - 有效IP: \${testResults.length}/\${originalIPs.length} (端口: \${selectedPort}, IP库: \${ipSourceName})\`;
}
// 新增加载IP列表的函数
async function loadIPs(ipSource) {
try {
const response = await fetch(\`/?loadIPs=\${ipSource}\`, {
method: 'GET'
});
if (!response.ok) {
throw new Error('Failed to load IPs');
}
const data = await response.json();
return data.ips || [];
} catch (error) {
console.error('加载IP列表失败:', error);
return [];
}
}
function displayResults() {
@@ -3368,6 +3625,18 @@ async function bestIP(request, env, txt = 'ADD.txt') {
</html>
`;
// 处理加载IP的请求
if (url.searchParams.get('loadIPs')) {
const ipSource = url.searchParams.get('loadIPs');
const ips = await GetCFIPs(ipSource);
return new Response(JSON.stringify({ ips }), {
headers: {
'Content-Type': 'application/json',
},
});
}
return new Response(html, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',