Skip to content

Commit f653c86

Browse files
authored
Add files via upload
1 parent 68e73ac commit f653c86

1 file changed

Lines changed: 295 additions & 0 deletions

File tree

scanner.html

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
<!doctype html>
2+
<html lang="fa" dir="rtl">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6+
<title>اسکنر IP تمیز</title>
7+
<script src="https://telegram.org/js/telegram-web-app.js"></script>
8+
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet">
9+
<style>
10+
:root{
11+
--bg:#06100d; --bg2:#0a1814; --card:#0e201a; --card2:#12281f; --border:#1c3a30;
12+
--text:#e8fff6; --muted:#7fae9e; --dim:#4f7c6c;
13+
--teal:#00e5a0; --teal2:#10b981; --cyan:#22d3ee; --amber:#fbbf24; --red:#f87171;
14+
--grad:linear-gradient(135deg,#00e5a0,#06b6d4);
15+
}
16+
*{box-sizing:border-box}
17+
html,body{margin:0;min-height:100%}
18+
body{font-family:'Vazirmatn',system-ui,sans-serif;color:var(--text);font-size:14px;
19+
background:radial-gradient(900px 500px at 50% -10%, rgba(0,229,160,.12), transparent 60%), var(--bg);
20+
padding:14px 14px calc(120px + env(safe-area-inset-bottom));-webkit-font-smoothing:antialiased}
21+
.muted{color:var(--muted)}.dim{color:var(--dim)}
22+
button{font-family:inherit;cursor:pointer}
23+
24+
.head{display:flex;align-items:center;gap:11px;margin-bottom:16px}
25+
.logo{width:44px;height:44px;border-radius:14px;background:var(--grad);display:grid;place-items:center;
26+
box-shadow:0 10px 26px -8px rgba(0,229,160,.7)}
27+
.logo svg{width:24px;height:24px;color:#04130d}
28+
.head h1{font-size:18px;margin:0;font-weight:800}
29+
.head p{margin:0;font-size:12px;color:var(--dim)}
30+
31+
.steps{display:flex;gap:6px;margin-bottom:16px}
32+
.steps .s{flex:1;height:4px;border-radius:4px;background:var(--card2)}
33+
.steps .s.on{background:var(--grad)}
34+
35+
.card{background:linear-gradient(180deg,var(--card),var(--bg2));border:1px solid var(--border);
36+
border-radius:18px;padding:16px;margin-bottom:14px}
37+
.card h3{margin:0 0 12px;font-size:15px;font-weight:800;display:flex;align-items:center;gap:8px}
38+
.row{display:flex;gap:10px}
39+
.field{flex:1}
40+
.field label{display:block;font-size:12px;color:var(--muted);font-weight:700;margin-bottom:6px}
41+
.field input,.field select{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:12px;
42+
padding:12px 13px;color:var(--text);font-family:inherit;font-size:14px}
43+
.chips{display:flex;flex-wrap:wrap;gap:7px;margin-top:6px}
44+
.chip{background:var(--card2);border:1px solid var(--border);border-radius:10px;padding:7px 12px;font-size:12.5px;font-weight:700;color:var(--muted)}
45+
.chip.on{background:var(--grad);color:#04130d;border-color:transparent}
46+
47+
.btn{border:0;border-radius:14px;padding:15px;font-weight:800;font-size:15px;width:100%;color:#04130d;background:var(--grad);
48+
box-shadow:0 14px 30px -12px rgba(0,229,160,.8);display:flex;align-items:center;justify-content:center;gap:9px}
49+
.btn.sec{background:var(--card2);color:var(--text);border:1px solid var(--border);box-shadow:none}
50+
.btn:disabled{opacity:.55}
51+
.btn.sm{padding:11px;font-size:13px;border-radius:12px}
52+
53+
.progress{height:10px;border-radius:10px;background:var(--card2);overflow:hidden;margin:14px 0 8px}
54+
.progress>i{display:block;height:100%;background:var(--grad);width:0;transition:width .25s}
55+
.statline{display:flex;justify-content:space-between;font-size:12.5px;color:var(--muted);font-weight:700}
56+
57+
.result{display:flex;align-items:center;gap:12px;padding:12px;border-radius:13px;background:var(--card2);border:1px solid var(--border);margin-bottom:9px}
58+
.result .rank{width:30px;height:30px;border-radius:9px;background:var(--bg);display:grid;place-items:center;font-weight:800;color:var(--teal);flex:none;font-size:13px}
59+
.result .ip{font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:.2px}
60+
.result .ms{margin-inline-start:auto;font-weight:800;font-size:14px}
61+
.ms.good{color:var(--teal)}.ms.mid{color:var(--amber)}.ms.bad{color:var(--red)}
62+
.result .copybtn{width:34px;height:34px;border-radius:9px;background:var(--bg);border:1px solid var(--border);display:grid;place-items:center;color:var(--muted);flex:none}
63+
64+
textarea{width:100%;min-height:120px;background:var(--bg);border:1px solid var(--border);border-radius:12px;
65+
padding:12px;color:var(--text);font-family:ui-monospace,Menlo,monospace;font-size:12px;resize:vertical;direction:ltr}
66+
.note{font-size:11.5px;color:var(--dim);margin-top:8px;line-height:1.7}
67+
68+
.bottombar{position:fixed;inset-inline:0;bottom:0;padding:12px 14px calc(12px + env(safe-area-inset-bottom));
69+
background:linear-gradient(0deg,var(--bg) 70%,transparent);display:flex;gap:10px}
70+
.hide{display:none!important}
71+
.toast{position:fixed;inset-inline:0;bottom:96px;margin:auto;width:max-content;max-width:90vw;background:var(--card2);
72+
border:1px solid var(--border);border-radius:12px;padding:11px 18px;font-weight:700;z-index:60;opacity:0;transform:translateY(8px);transition:.25s}
73+
.toast.show{opacity:1;transform:none}
74+
.empty{text-align:center;color:var(--dim);padding:30px 10px;font-weight:600}
75+
.spin{width:16px;height:16px;border:2px solid rgba(4,19,13,.3);border-top-color:#04130d;border-radius:50%;animation:sp .7s linear infinite}
76+
@keyframes sp{to{transform:rotate(360deg)}}
77+
@media(prefers-reduced-motion:reduce){.spin{animation:none}}
78+
</style>
79+
</head>
80+
<body>
81+
82+
<div class="head">
83+
<div class="logo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 12h4l3 8 4-16 3 8h4"/></svg></div>
84+
<div><h1>اسکنر IP تمیز</h1><p>پیدا کردن بهترین IP کلودفلر برای کانفیگ‌ها</p></div>
85+
</div>
86+
87+
<div class="steps"><div class="s on" id="st1"></div><div class="s" id="st2"></div><div class="s" id="st3"></div></div>
88+
89+
<section id="step1">
90+
<div class="card">
91+
<h3>⚙️ تنظیمات اسکن</h3>
92+
<div class="row">
93+
<div class="field"><label>پورت</label>
94+
<select id="port"><option>443</option><option>2053</option><option>2083</option><option>2087</option><option>2096</option><option>8443</option></select>
95+
</div>
96+
<div class="field"><label>تعداد IP برای تست</label>
97+
<select id="count"><option value="40">۴۰</option><option value="80" selected>۸۰</option><option value="150">۱۵۰</option><option value="300">۳۰۰</option></select>
98+
</div>
99+
</div>
100+
<div style="margin-top:12px"><label class="muted" style="font-size:12px;font-weight:700">حداکثر تأخیر قابل‌قبول</label>
101+
<div class="chips" id="maxms">
102+
<span class="chip" data-v="200">۲۰۰ms</span>
103+
<span class="chip on" data-v="400">۴۰۰ms</span>
104+
<span class="chip" data-v="800">۸۰۰ms</span>
105+
<span class="chip" data-v="3000">همه</span>
106+
</div>
107+
</div>
108+
<p class="note">اسکن کاملاً داخل همین اپ و روی شبکه‌ی شما انجام می‌شه — هیچ سروری در کار نیست. نتایج به اتصال فعلی شما بستگی داره.</p>
109+
</div>
110+
<div class="card">
111+
<h3>📊 وضعیت</h3>
112+
<div class="progress"><i id="bar"></i></div>
113+
<div class="statline"><span id="scanState">آماده برای اسکن</span><span id="scanCount">۰ / ۰</span></div>
114+
<div class="statline" style="margin-top:6px"><span class="dim">سالم پیدا شده</span><span id="foundCount" style="color:var(--teal)">۰</span></div>
115+
</div>
116+
</section>
117+
118+
<section id="step2" class="hide">
119+
<div class="card">
120+
<h3>✅ بهترین IPها <span id="resCount" class="muted" style="font-weight:600;font-size:12px"></span></h3>
121+
<div id="results"></div>
122+
</div>
123+
</section>
124+
125+
<section id="step3" class="hide">
126+
<div class="card">
127+
<h3>🛠 ساخت کانفیگ با IP تمیز</h3>
128+
<label class="muted" style="font-size:12px;font-weight:700">کانفیگ‌هات رو بذار (هر کدوم یک خط: vless/vmess/trojan/ss)</label>
129+
<textarea id="cfgIn" placeholder="vless://...&#10;vmess://...&#10;trojan://..."></textarea>
130+
<div class="row" style="margin-top:10px">
131+
<button class="btn sm sec" id="useBest">استفاده از بهترین IP</button>
132+
<button class="btn sm" id="buildBtn">ساخت کانفیگ‌ها</button>
133+
</div>
134+
</div>
135+
<div class="card" id="outCard" style="display:none">
136+
<h3>📤 خروجی</h3>
137+
<textarea id="cfgOut" readonly></textarea>
138+
<div class="row" style="margin-top:10px">
139+
<button class="btn sm sec" id="copyClean">کپی بدون پینگ</button>
140+
<button class="btn sm" id="copyPing">کپی با پینگ</button>
141+
</div>
142+
</div>
143+
</section>
144+
145+
<div class="bottombar" id="bar1">
146+
<button class="btn" id="scanBtn"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#04130d" stroke-width="2.4"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4-4"/></svg> شروع اسکن</button>
147+
</div>
148+
<div class="bottombar hide" id="bar2">
149+
<button class="btn sec" id="rescan">اسکن دوباره</button>
150+
<button class="btn" id="toOpt">ساخت کانفیگ ←</button>
151+
</div>
152+
<div class="bottombar hide" id="bar3">
153+
<button class="btn sec" id="backRes">← نتایج</button>
154+
</div>
155+
156+
<div class="toast" id="toast"></div>
157+
158+
<script>
159+
const tg = window.Telegram && window.Telegram.WebApp;
160+
if(tg){ try{ tg.ready(); tg.expand(); }catch(e){} }
161+
162+
const $=s=>document.querySelector(s), $$=s=>document.querySelectorAll(s);
163+
const fa=n=>String(n).replace(/[0-9]/g,d=>"۰۱۲۳۴۵۶۷۸۹"[d]);
164+
let toastT; function toast(m){const t=$("#toast");t.textContent=m;t.classList.add("show");clearTimeout(toastT);toastT=setTimeout(()=>t.classList.remove("show"),2000);if(tg&&tg.HapticFeedback)try{tg.HapticFeedback.impactOccurred('light')}catch(e){}}
165+
166+
const CF_FALLBACK=["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"];
167+
async function getRanges(){
168+
try{
169+
const r=await fetch("https://www.cloudflare.com/ips-v4",{cache:"no-store"});
170+
const t=await r.text(); const list=t.split("\n").map(s=>s.trim()).filter(s=>/^\d+\.\d+\.\d+\.\d+\/\d+$/.test(s));
171+
return list.length?list:CF_FALLBACK;
172+
}catch(e){ return CF_FALLBACK; }
173+
}
174+
const ipToInt=ip=>ip.split(".").reduce((a,o)=>(a<<8>>>0)+(+o),0)>>>0;
175+
const intToIp=n=>[24,16,8,0].map(s=>(n>>>s)&255).join(".");
176+
function randomIPs(ranges,n){
177+
const out=new Set(); let guard=0;
178+
while(out.size<n && guard++<n*40){
179+
const cidr=ranges[Math.floor(Math.random()*ranges.length)];
180+
const [base,bits]=cidr.split("/"); const size=2**(32-(+bits));
181+
const ip=intToIp((ipToInt(base)+Math.floor(Math.random()*size))>>>0);
182+
if(!ip.endsWith(".0")&&!ip.endsWith(".255")) out.add(ip);
183+
}
184+
return [...out];
185+
}
186+
187+
function pingIP(ip,port,timeout=2200){
188+
return new Promise(resolve=>{
189+
const ctrl=new AbortController(); const t0=performance.now();
190+
const to=setTimeout(()=>ctrl.abort(),timeout);
191+
fetch(`https://${ip}:${port}/cdn-cgi/trace`,{mode:"no-cors",cache:"no-store",signal:ctrl.signal})
192+
.then(()=>{clearTimeout(to);resolve(Math.round(performance.now()-t0));})
193+
.catch(err=>{clearTimeout(to); if(err.name==="AbortError") resolve(null); else resolve(Math.round(performance.now()-t0));});
194+
});
195+
}
196+
async function pool(items,worker,concurrency,onTick){
197+
let i=0,done=0; const res=[];
198+
async function run(){ while(i<items.length){ const idx=i++; res[idx]=await worker(items[idx]); onTick(++done); } }
199+
await Promise.all(Array.from({length:Math.min(concurrency,items.length)},run));
200+
return res;
201+
}
202+
203+
let FOUND=[], MAXMS=400, scanning=false;
204+
$("#maxms").addEventListener("click",e=>{const c=e.target.closest(".chip");if(!c)return;$$("#maxms .chip").forEach(x=>x.classList.remove("on"));c.classList.add("on");MAXMS=+c.dataset.v;});
205+
206+
function setStep(n){
207+
[1,2,3].forEach(i=>{$("#step"+i).classList.toggle("hide",i!==n);$("#bar"+i).classList.toggle("hide",i!==n);$("#st"+i).classList.toggle("on",i<=n);});
208+
if(tg){ try{ if(n>1){tg.BackButton.show();}else{tg.BackButton.hide();} }catch(e){} }
209+
window.scrollTo(0,0);
210+
}
211+
212+
async function startScan(){
213+
if(scanning) return; scanning=true; FOUND=[];
214+
const port=$("#port").value, count=+$("#count").value;
215+
$("#scanBtn").disabled=true; $("#scanBtn").innerHTML='<span class="spin"></span> در حال اسکن…';
216+
$("#scanState").textContent="در حال آماده‌سازی…"; $("#bar").style.width="0%";
217+
const ranges=await getRanges();
218+
const ips=randomIPs(ranges,count);
219+
$("#scanState").textContent="در حال تست IPها…";
220+
await pool(ips,async ip=>{
221+
const ms=await pingIP(ip,port,2200);
222+
if(ms!=null && ms<=MAXMS) FOUND.push({ip,ms});
223+
return ms;
224+
},24,(done)=>{
225+
$("#bar").style.width=(done/ips.length*100)+"%";
226+
$("#scanCount").textContent=fa(done)+" / "+fa(ips.length);
227+
$("#foundCount").textContent=fa(FOUND.length);
228+
});
229+
FOUND.sort((a,b)=>a.ms-b.ms);
230+
scanning=false; $("#scanBtn").disabled=false;
231+
$("#scanBtn").innerHTML='<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#04130d" stroke-width="2.4"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4-4"/></svg> شروع اسکن';
232+
$("#scanState").textContent="اسکن کامل شد";
233+
renderResults();
234+
if(FOUND.length) setStep(2); else toast("IP سالمی پیدا نشد — حد تأخیر رو بیشتر کن یا دوباره بزن");
235+
}
236+
function msClass(ms){return ms<=200?"good":ms<=500?"mid":"bad";}
237+
function renderResults(){
238+
$("#resCount").textContent="("+fa(FOUND.length)+" مورد)";
239+
const top=FOUND.slice(0,40);
240+
$("#results").innerHTML = top.length? top.map((r,i)=>`
241+
<div class="result">
242+
<span class="rank">${fa(i+1)}</span>
243+
<span class="ip" dir="ltr">${r.ip}</span>
244+
<span class="ms ${msClass(r.ms)}">${fa(r.ms)}ms</span>
245+
<button class="copybtn" data-ip="${r.ip}"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg></button>
246+
</div>`).join("") : `<div class="empty">چیزی پیدا نشد.</div>`;
247+
}
248+
$("#results").addEventListener("click",e=>{const b=e.target.closest("[data-ip]");if(!b)return;copy(b.dataset.ip);toast("کپی شد: "+b.dataset.ip);});
249+
250+
function replaceHost(link,addr,port){
251+
link=link.trim();
252+
if(/^(vless|trojan):\/\//.test(link)){
253+
return link.replace(/^((?:vless|trojan):\/\/[^@]+@)([^:\/?#]+)(?::(\d+))?/,(m,p,h,pt)=>`${p}${addr}:${port||pt||443}`);
254+
}
255+
if(link.startsWith("vmess://")){
256+
try{const o=JSON.parse(atob(link.slice(8).replace(/-/g,'+').replace(/_/g,'/')+"=="));o.add=addr;if(port)o.port=String(port);return "vmess://"+btoa(JSON.stringify(o));}catch(e){return null;}
257+
}
258+
if(link.startsWith("ss://")){
259+
const m=link.match(/^(ss:\/\/[^@]+@)([^:\/?#]+)(?::(\d+))?(.*)$/);
260+
if(m) return `${m[1]}${addr}:${port||m[3]||443}${m[4]}`;
261+
}
262+
return null;
263+
}
264+
function tagPing(link,ms){
265+
if(link.startsWith("vmess://")){try{const o=JSON.parse(atob(link.slice(8)+"=="));o.ps=(o.ps||"node")+` (${ms}ms)`;return "vmess://"+btoa(JSON.stringify(o));}catch(e){return link;}}
266+
return link.includes("#")? link+encodeURIComponent(` (${ms}ms)`) : link+"#"+encodeURIComponent(`clean (${ms}ms)`);
267+
}
268+
let OUT_CLEAN=[], OUT_PING=[];
269+
function build(){
270+
const lines=$("#cfgIn").value.split("\n").map(s=>s.trim()).filter(Boolean);
271+
if(!lines.length){toast("اول کانفیگ بذار");return;}
272+
if(!FOUND.length){toast("اول اسکن کن");return;}
273+
const port=$("#port").value, ips=FOUND.slice(0,10);
274+
OUT_CLEAN=[];OUT_PING=[];
275+
for(const ln of lines){ for(const r of ips){ const nl=replaceHost(ln,r.ip,port); if(nl){OUT_CLEAN.push(nl);OUT_PING.push(tagPing(nl,r.ms));} } }
276+
if(!OUT_CLEAN.length){toast("کانفیگ معتبری نبود");return;}
277+
$("#outCard").style.display="block";
278+
$("#cfgOut").value=OUT_PING.join("\n");
279+
toast(fa(OUT_CLEAN.length)+" کانفیگ ساخته شد");
280+
}
281+
function copy(t){ if(navigator.clipboard) navigator.clipboard.writeText(t).catch(()=>{}); else {const a=document.createElement("textarea");a.value=t;document.body.appendChild(a);a.select();document.execCommand("copy");a.remove();} }
282+
283+
$("#scanBtn").onclick=startScan;
284+
$("#rescan").onclick=()=>setStep(1);
285+
$("#toOpt").onclick=()=>setStep(3);
286+
$("#backRes").onclick=()=>setStep(2);
287+
$("#useBest").onclick=()=>{ if(FOUND[0]) toast("بهترین IP: "+FOUND[0].ip); };
288+
$("#buildBtn").onclick=build;
289+
$("#copyClean").onclick=()=>{copy(OUT_CLEAN.join("\n"));toast("کپی شد (بدون پینگ)");};
290+
$("#copyPing").onclick=()=>{copy(OUT_PING.join("\n"));toast("کپی شد (با پینگ)");};
291+
if(tg){ try{ tg.BackButton.onClick(()=>{ const cur=$("#step3").classList.contains("hide")?($("#step2").classList.contains("hide")?1:2):3; setStep(Math.max(1,cur-1)); }); }catch(e){} }
292+
setStep(1);
293+
</script>
294+
</body>
295+
</html>

0 commit comments

Comments
 (0)