arblant
cf-clean-ip-scanner / index.html
index.html200 lines · 12.9 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#0b1020">
<title>جارو 🧹 | اسکنر آی‌پی تمیز کلادفلر</title>
<link rel="manifest" href="manifest.json">
<link rel="icon" href="icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="icon.svg">
<style>
  * { box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
  :root { --txt:#eef2ff; --muted:#94a3b8; --card:rgba(255,255,255,.06); --line:rgba(255,255,255,.1); }
  body { margin:0; font-family:Tahoma,'Segoe UI',sans-serif; color:var(--txt); background:#0b1020; min-height:100vh; overflow-x:hidden; }
  .aurora { position:fixed; inset:0; z-index:-1; overflow:hidden; }
  .blob { position:absolute; border-radius:50%; filter:blur(90px); opacity:.5; animation:drift 14s ease-in-out infinite alternate; }
  .b1 { width:420px;height:420px; background:#7c3aed; top:-120px; right:-100px; }
  .b2 { width:380px;height:380px; background:#0ea5e9; bottom:-120px; left:-100px; animation-delay:-5s; }
  .b3 { width:260px;height:260px; background:#ec4899; top:40%; left:30%; opacity:.3; animation-delay:-9s; }
  @keyframes drift { from{transform:translate(0,0) scale(1)} to{transform:translate(40px,30px) scale(1.15)} }
  .wrap { max-width:720px; margin:0 auto; padding:20px 16px 60px; }
  header { text-align:center; padding:26px 0 10px; }
  .logo { font-size:3rem; filter:drop-shadow(0 6px 20px rgba(124,58,237,.6)); animation:float 3s ease-in-out infinite; }
  @keyframes float { 50%{transform:translateY(-8px)} }
  h1 { margin:8px 0 4px; font-size:1.6rem; background:linear-gradient(90deg,#c4b5fd,#7dd3fc,#f9a8d4); -webkit-background-clip:text; background-clip:text; color:transparent; }
  .sub { color:var(--muted); font-size:.85rem; line-height:1.9; }
  .glass { background:var(--card); border:1px solid var(--line); border-radius:20px; padding:18px; backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px); box-shadow:0 8px 32px rgba(0,0,0,.35); margin:14px 0; }
  .seg { display:flex; background:rgba(0,0,0,.3); border-radius:14px; padding:4px; gap:4px; }
  .seg button { flex:1; border:none; background:transparent; color:var(--muted); padding:10px 4px; border-radius:10px; font-family:inherit; font-size:.9rem; cursor:pointer; transition:.2s; }
  .seg button.on { background:linear-gradient(135deg,#7c3aed,#0ea5e9); color:#fff; font-weight:bold; box-shadow:0 4px 14px rgba(124,58,237,.5); }
  .big { width:100%; margin-top:12px; padding:16px; font-size:1.15rem; font-weight:bold; font-family:inherit; border:none; border-radius:16px; cursor:pointer; color:#fff; background:linear-gradient(135deg,#7c3aed,#2563eb,#0ea5e9); background-size:200% 200%; animation:sheen 4s linear infinite; box-shadow:0 8px 26px rgba(37,99,235,.45); }
  @keyframes sheen { 50%{background-position:100% 100%} }
  .big:disabled { opacity:.55; animation:none; }
  .ghost { background:rgba(255,255,255,.07); border:1px solid var(--line); color:var(--txt); border-radius:12px; padding:10px 16px; font-family:inherit; cursor:pointer; }
  .pbar { height:12px; background:rgba(0,0,0,.35); border-radius:8px; overflow:hidden; margin-top:14px; }
  .pbar > div { height:100%; width:0%; border-radius:8px; background:linear-gradient(90deg,#7c3aed,#0ea5e9,#4ade80); transition:width .25s; }
  .stats { display:grid; grid-template-columns:repeat(3,1fr); gap:10px; margin-top:14px; }
  .stat { background:rgba(0,0,0,.25); border-radius:14px; padding:10px; text-align:center; }
  .stat .v { font-size:1.25rem; font-weight:bold; }
  .stat .l { font-size:.72rem; color:var(--muted); margin-top:2px; }
  .live { text-align:center; color:var(--muted); font-size:.8rem; margin-top:10px; min-height:1.4em; font-family:monospace; direction:ltr; }
  .rip { display:flex; align-items:center; gap:12px; background:rgba(0,0,0,.25); border:1px solid var(--line); border-radius:16px; padding:12px 14px; margin:8px 0; animation:pop .3s ease; }
  @keyframes pop { from{transform:scale(.96); opacity:0} }
  .rank { width:34px; height:34px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-weight:bold; font-size:.9rem; flex-shrink:0; background:linear-gradient(135deg,#f59e0b,#ec4899); }
  .rip:nth-child(1) .rank { background:linear-gradient(135deg,#fbbf24,#f59e0b); box-shadow:0 0 14px rgba(251,191,36,.6); }
  .rip:nth-child(2) .rank { background:linear-gradient(135deg,#e2e8f0,#94a3b8); }
  .rip:nth-child(3) .rank { background:linear-gradient(135deg,#d97706,#92400e); }
  .ip { font-family:monospace; font-size:1.05rem; direction:ltr; }
  .rng { font-size:.7rem; color:var(--muted); font-family:monospace; direction:ltr; }
  .pill { margin-inline-start:auto; font-weight:bold; font-size:.9rem; padding:6px 12px; border-radius:20px; direction:ltr; flex-shrink:0; }
  .p-good { background:rgba(74,222,128,.15); color:#4ade80; border:1px solid rgba(74,222,128,.4); }
  .p-mid { background:rgba(251,191,36,.12); color:#fbbf24; border:1px solid rgba(251,191,36,.4); }
  .p-bad { background:rgba(248,113,113,.12); color:#f87171; border:1px solid rgba(248,113,113,.4); }
  .cp { border:1px solid var(--line); background:rgba(255,255,255,.06); color:#7dd3fc; border-radius:10px; padding:8px 12px; font-family:inherit; cursor:pointer; flex-shrink:0; }
  .tools { display:flex; gap:8px; margin-bottom:6px; align-items:center; }
  .tools b { font-size:1rem; }
  .hint { font-size:.8rem; color:var(--muted); line-height:2; }
  .hint a { color:#7dd3fc; }
  #installBtn { display:none; width:100%; margin-top:10px; }
  .foot { text-align:center; color:var(--muted); font-size:.75rem; margin-top:20px; }
</style>
</head>
<body>
<div class="aurora"><div class="blob b1"></div><div class="blob b2"></div><div class="blob b3"></div></div>
<div class="wrap">
  <header>
    <div class="logo">🧹</div>
    <h1>جارو — اسکنر آی‌پی تمیز</h1>
    <div class="sub">تمیزترین آی‌پی‌های لبه‌ی کلادفلر، تست‌شده از اینترنت <b>خودت</b><br>مخصوص همین شبکه (همراه اول / ایرانسل / ...)</div>
  </header>

  <div class="glass">
    <div class="seg" id="seg">
      <button data-n="60">⚡ سریع<span style="display:block;font-size:.7rem">۶۰ آی‌پی</span></button>
      <button data-n="150" class="on">🔍 معمولی<span style="display:block;font-size:.7rem">۱۵۰ آی‌پی</span></button>
      <button data-n="300">🌊 عمیق<span style="display:block;font-size:.7rem">۳۰۰ آی‌پی</span></button>
    </div>
    <button class="big" id="start">شروع اسکن ✨</button>
    <button class="ghost" id="installBtn">📲 نصب اپ روی گوشی / کامپیوتر</button>
    <div class="pbar"><div id="barfill"></div></div>
    <div class="stats">
      <div class="stat"><div class="v" id="stTested">۰</div><div class="l">تست‌شده</div></div>
      <div class="stat"><div class="v" id="stOk" style="color:#4ade80">۰</div><div class="l">سالم</div></div>
      <div class="stat"><div class="v" id="stBest" style="color:#7dd3fc">—</div><div class="l">بهترین پینگ</div></div>
    </div>
    <div class="live" id="live"></div>
  </div>

  <div class="glass" id="resCard" style="display:none">
    <div class="tools"><b>🏆 تمیزترین‌ها</b><span style="flex:1"></span>
      <button class="ghost" id="dlTxt">TXT</button>
      <button class="ghost" id="dlCsv">CSV</button>
    </div>
    <div id="list"></div>
  </div>

  <div class="glass hint">
    💡 «تمیز» = آی‌پی‌ای که از شبکه‌ی تو سریع و بدون اختلال به لبه‌ی کلادفلر می‌رسه. رنج‌ها از
    <a href="https://www.cloudflare.com/ips-v4">لیست رسمی کلادفلر</a> هستن و هر روز خودکار تازه می‌شن.
    تست با درخواست HTTP به پورت ۸۰ انجام می‌شه — فقط «زمان اتصال» مهمه.<br>
    ⚠️ این ابزار پینگ و دسترسی رو می‌سنجه؛ آی‌پی خوبِ امروز ممکنه فردا کثیف بشه.
  </div>
  <div class="foot">ساخته شده با 🧹 برای اینترنت آزاد</div>
</div>

<script id="cfdata" type="application/json">
{"ranges":["173.245.48.0/20","103.21.244.0/22","103.22.200.0/22","103.31.4.0/22","141.101.64.0/18","108.162.192.0/18","190.93.240.0/20","188.114.96.0/20","197.234.240.0/22","198.41.128.0/17","162.158.0.0/15","104.16.0.0/13","104.24.0.0/14","172.64.0.0/13","131.0.72.0/22"]}
</script>
<script>
const RANGES_URL='data/cf-ips.json', TIMEOUT_MS=6000, CONCURRENCY=12;
let ranges=[], stopFlag=false, results=[], sampleN=150;
const fa=n=>Number(n).toLocaleString('fa-IR'), $=id=>document.getElementById(id);

async function loadRanges(){
  try{ ranges=(await (await fetch(RANGES_URL)).json()).ranges; }
  catch(e){ ranges=JSON.parse(document.getElementById('cfdata').textContent).ranges; }
}
const ipToInt=ip=>ip.split('.').reduce((a,o)=>(a<<8)+ +o,0)>>>0;
const intToIp=n=>[(n>>>24)&255,(n>>>16)&255,(n>>>8)&255,n&255].join('.');
function randomIp(cidr){
  const [base,p]=cidr.split('/'), bits=+p, net=ipToInt(base), hosts=Math.pow(2,32-bits);
  const h = hosts<=2 ? 0 : 1+Math.floor(Math.random()*(hosts-2));
  return {ip:intToIp((net+h)>>>0), range:cidr};
}
function sampleIps(n){
  const out=[], per=Math.ceil(n/ranges.length);
  for(const r of ranges) for(let i=0;i<per&&out.length<n;i++) out.push(randomIp(r));
  for(let i=out.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[out[i],out[j]]=[out[j],out[i]];}
  return out.slice(0,n);
}
function probe(ip){
  return new Promise(res=>{
    const t0=performance.now(), ctl=new AbortController();
    const to=setTimeout(()=>{ctl.abort();res(null);},TIMEOUT_MS);
    fetch('http://'+ip+'/',{mode:'no-cors',cache:'no-store',signal:ctl.signal})
      .then(()=>{clearTimeout(to);res(Math.round(performance.now()-t0));})
      .catch(()=>{clearTimeout(to);res(null);});
  });
}
const pill=ms=>ms<300?'p-good':ms<800?'p-mid':'p-bad';
let scanning=false;
async function run(){
  if(scanning){ stopFlag=true; return; } // click while scanning = stop
  scanning=true; stopFlag=false; results=[];
  const btn=$('start'); btn.textContent='توقف ⏹';
  $('resCard').style.display='none'; $('list').innerHTML='';
  const ips=sampleIps(sampleN);
  let done=0, ok=0, best=Infinity;
  const render=()=>{ $('barfill').style.width=(done/ips.length*100)+'%';
    $('stTested').textContent=fa(done); $('stOk').textContent=fa(ok);
    $('stBest').textContent=best===Infinity?'—':fa(best)+'ms'; };
  render(); let idx=0;
  async function worker(){
    while(idx<ips.length && !stopFlag){
      const s=ips[idx++];
      $('live').textContent='probing '+s.ip+' ...';
      const ms=await probe(s.ip); done++;
      if(ms!==null){ok++; if(ms<best)best=ms; results.push({...s,ms});}
      render();
    }
  }
  await Promise.all(Array.from({length:CONCURRENCY},worker));
  $('live').textContent=stopFlag?'متوقف شد.':'تمام شد ✅ '+fa(ok)+' آی‌پی سالم پیدا شد';
  results.sort((a,b)=>a.ms-b.ms); showResults();
  scanning=false; btn.textContent='اسکن دوباره 🔄';
}
function showResults(){
  if(!results.length)return;
  $('resCard').style.display='';
  $('list').innerHTML=results.slice(0,50).map((r,i)=>
    `<div class="rip"><div class="rank">${fa(i+1)}</div>
     <div><div class="ip">${r.ip}</div><div class="rng">${r.range}</div></div>
     <div class="pill ${pill(r.ms)}">${fa(r.ms)}ms</div>
     <button class="cp" data-ip="${r.ip}">کپی</button></div>`).join('');
  document.querySelectorAll('.cp').forEach(b=>b.onclick=()=>{
    navigator.clipboard.writeText(b.dataset.ip);
    b.textContent='✓'; setTimeout(()=>b.textContent='کپی',1200);});
}
function dl(name,text){const a=document.createElement('a');
  a.href=URL.createObjectURL(new Blob([text],{type:'text/plain'}));a.download=name;a.click();
  setTimeout(()=>URL.revokeObjectURL(a.href),2000);}
$('dlTxt').onclick=()=>dl('clean-ips.txt',results.map(r=>r.ip).join('\n'));
$('dlCsv').onclick=()=>dl('clean-ips.csv','ip,latency_ms,range\n'+results.map(r=>`${r.ip},${r.ms},${r.range}`).join('\n'));
$('start').onclick=()=>run();
document.querySelectorAll('#seg button').forEach(b=>b.onclick=()=>{
  document.querySelectorAll('#seg button').forEach(x=>x.classList.remove('on'));
  b.classList.add('on'); sampleN=+b.dataset.n; });

// PWA install
let deferredPrompt=null;
window.addEventListener('beforeinstallprompt',e=>{e.preventDefault();deferredPrompt=e;$('installBtn').style.display='';});
$('installBtn').onclick=async()=>{
  if(deferredPrompt){deferredPrompt.prompt();await deferredPrompt.userChoice;deferredPrompt=null;$('installBtn').style.display='none';}
  else alert('از منوی مرورگر گزینه‌ی "Add to Home Screen" (گوشی) یا "Install" (کامپیوتر) رو بزن 📲');
};
if('serviceWorker' in navigator) window.addEventListener('load',()=>navigator.serviceWorker.register('sw.js').catch(()=>{}));
loadRanges().catch(()=>{$('live').textContent='خطا در لود رنج‌ها!';});
</script>
</body>
</html>