Repository: MikeWang000000/ClashScan Branch: gh-pages Commit: ae3b3c9a63ec Files: 6 Total size: 20.5 KB Directory structure: gitextract_n6tev4bh/ ├── index.html ├── manifest.json ├── scannerWorker.js ├── script.js ├── service-worker.js └── styles.css ================================================ FILE CONTENTS ================================================ ================================================ FILE: index.html ================================================ 在线 Clash 检测

您是否在使用 Clash?

任何网站都能检测您是否正在使用 Clash。

使用说明


检测结果

Clash 版本:

暂无数据


================================================ FILE: manifest.json ================================================ { "name": "Clash Scan", "short_name": "ClashScan", "start_url": "ClashScan/index.html", "display": "standalone", "background_color": "#ffffff", "theme_color": "#000000", "icons": [ { "src": "/ClashScan/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/ClashScan/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png" } ] } ================================================ FILE: scannerWorker.js ================================================ // scannerWorker.js self.onmessage = async function (e) { const { port, timeout } = e.data; let foundPort = 0; const controller = new AbortController(); const signal = controller.signal; async function checkIsClash(port) { try { const response = await fetch( "http://127.0.0.1:" + port, { method: "GET", signal: signal } ); const dat = await response.json(); if (Object.keys(dat).length === 1 && (dat.message === "Unauthorized" || dat.hello)) { return true; } return false; } catch (error) { if (error.name === 'AbortError') { console.log('Fetch aborted'); } return false; } } const timeoutId = setTimeout(() => controller.abort(), timeout); if (await checkIsClash(port)) { foundPort = port; } clearTimeout(timeoutId); self.postMessage({ foundPort, port }); }; self.onterminate = function () { controller.abort(); }; ================================================ FILE: script.js ================================================ if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('service-worker.js') .then(registration => { console.log('ServiceWorker registration successful with scope: ', registration.scope); }, error => { console.log('ServiceWorker registration failed: ', error); }); }); } if (!AbortSignal.timeout) { AbortSignal.timeout = function (ms) { const controller = new AbortController(); setTimeout(() => controller.abort(new DOMException("TimeoutError")), ms); return controller.signal; }; } function avg (arr) { let sum = 0; arr.forEach((k) => { sum += k; }) return sum / arr.length; } function shuffle(array) { for (let i = array.length - 1; i >= 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } function randArr(start, end, length) { return shuffle(range(start, end)).slice(0, length); } function range(start, end) { return Array.from({ length: end - start }, (v, i) => i + start); } function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } async function portTime(port) { const st = performance.now(); try { await fetch( "http://127.0.0.1:" + port, { signal: AbortSignal.timeout(3000) } ); } catch (error) {} const et = performance.now(); return et - st; } async function guessOpenProxyPort() { const promList = []; const randTime = []; const msThresh = 300; randArr(41000, 49000, 8).forEach((port) => { promList.push(portTime(port)); }); const prom7890 = portTime(7890); const prom7897 = portTime(7897); for (const prom of promList) { randTime.push(await prom); } closedAvg = avg(randTime); if (Math.abs(await prom7890 - closedAvg) > msThresh) { return 7890; } if (Math.abs(await prom7897 - closedAvg) > msThresh) { return 7897; } return 0; } async function guessClashVersion(port) { let hasError = false; let version = "Clash Core Unknown"; async function pathExists(path) { try { const response = await fetch( "http://127.0.0.1:" + port + path, { method: "GET", signal: AbortSignal.timeout(500) } ); if (response.ok || response.status === 401) { return true; } return false; } catch (error) { hasError = true; return false; } } if (await pathExists("/memory")) { version = "Clash Meta Core v1.14.4 ~ Latest (guessed)"; } else if (await pathExists("/restart")) { version = "Clash Meta Core v1.14.3 (guessed)"; } else if (await pathExists("/dns")) { version = "Clash Meta Core v1.14.2 (guessed)"; } else if (await pathExists("/group")) { version = "Clash Meta Core v1.12.0 ~ v1.14.1 (guessed)"; } else if (await pathExists("/cache")) { version = "Clash Meta Core v1.10.0 ~ v1.11.8 (guessed)"; } else if (await pathExists("/script")) { version = "Clash Meta Core v1.9.1 (guessed)"; } else if (await pathExists("/providers/rules")) { version = "Clash Meta Core v1.8.0 ~ v1.9.0 (guessed)"; } else if (await pathExists("/providers/proxies")) { version = "Clash Core v0.17.0 ~ Latest (guessed)"; } else if (await pathExists("/version")) { version = "Clash Core v0.16.0 (guessed)"; } else { version = "Clash Core Unknown"; } if (hasError) { version = "Clash Core Unknown"; } return version; } async function getClashVersion(port) { try { const response = await fetch( "http://127.0.0.1:" + port + "/version", { method: "GET", signal: AbortSignal.timeout(500) } ); const dat = await response.json(); if (response.ok) { let brand = "Clash Core"; if (dat.meta) { brand = "Clash Meta Core"; } else if (dat.premium) { brand = "Clash Core Premium"; } return brand + " " + dat.version; } return await guessClashVersion(port); } catch (error) { return "Clash Core Unknown"; } } async function getClashTraffic(port) { try { const hostlist = []; const response = await fetch( "http://127.0.0.1:" + port + "/connections", { method: "GET", signal: AbortSignal.timeout(500) } ); const dat = await response.json(); if (Array.isArray(dat.connections)) { dat.connections.forEach((conn) => { let addr = conn.metadata.host ? conn.metadata.host : conn.metadata.destinationIP; let port = parseInt(conn.metadata.destinationPort, 10); if (port !== 80 && port !== 443) { addr += (":" + port); } hostlist.push(addr); }); return hostlist; } return null; } catch (error) { return null; } } async function getClashProxies(port) { try { const response = await fetch( "http://127.0.0.1:" + port + "/proxies", { method: "GET", signal: AbortSignal.timeout(500) } ); if (!response.ok) { return null; } return await response.json(); } catch (error) { return null; } } async function scanLocalhost(workerNum) { window.scanning = true; let proxyPort = 0; let workerDone = 0; let totalScannedPorts = 0; let foundPort = 0; const ports = [9090] .concat(range(9091, 10000)) .concat(range(2000, 9090).reverse()) .concat(range(10000, 65536)) .concat(range(1, 2000).reverse()); const workers = []; const commonPortLength = 3200; let percentage = 0; let currentPortIndex = 0; function updateProgress() { let preInfo = ""; if (proxyPort === 7890) { preInfo = "TCP/7890 开放 (Clash?) | "; } else if (proxyPort === 7897) { preInfo = "TCP/7897 开放 (Clash Verge?) | "; } if (totalScannedPorts < commonPortLength) { percentage = Math.round(100 * totalScannedPorts / commonPortLength); document.querySelector("#title").innerText = preInfo + "扫描中... " + percentage + "%"; document.querySelector(".progress-done").style.width = percentage + "%"; } else { percentage = Math.round( 100 * (totalScannedPorts - commonPortLength) / ports.length ); document.querySelector("#title").innerText = preInfo + "扩展扫描中... " + percentage + "%"; document.querySelector(".progress-done").style.width = percentage + "%"; } } async function updateInfoWorker() { let preInfo = ""; if (proxyPort === 7890) { preInfo = "TCP/7890 开放 (Clash?) | "; } else if (proxyPort === 7897) { preInfo = "TCP/7897 开放 (Clash Verge?) | "; } document.querySelector("#txt_version").innerText = "暂无数据"; document.querySelector("#txt_hosts").innerHTML = ""; document.querySelector("#div_hosts").style.display = "none"; document.querySelector("#txt_servers").innerHTML = ""; document.querySelector("#div_servers").style.display = "none"; while (!foundPort && workerDone !== workerNum) { updateProgress(); await sleep(500); } window.scanning = false; document.querySelector(".progress-done").style.width = "100%"; if (foundPort) { document.querySelector("#title").innerText = "您在使用 Clash:TCP/" + foundPort; document.querySelector("#txt_version").innerText = await getClashVersion(foundPort); const hostlist = await getClashTraffic(foundPort); if (hostlist && Array.isArray(hostlist)) { document.querySelector("#txt_hosts").innerHTML = hostlist.join("
"); document.querySelector("#div_hosts").style.display = "inherit"; } const proxies = await getClashProxies(foundPort); if (proxies) { document.querySelector("#txt_servers").innerHTML = JSON.stringify(proxies, null, 4); document.querySelector("#div_servers").style.display = "inherit"; } } else if (proxyPort) { document.querySelector("#title").innerText = preInfo + "扫描完毕"; } else { document.querySelector("#title").innerText = "未发现 Clash"; } } function terminateAllWorkers() { workers.forEach(worker => worker.terminate()); } updateInfoWorker(); proxyPort = await guessOpenProxyPort(); const portChunks = Array.from({ length: workerNum }, () => []); for (let i = 0; i < ports.length; i++) { portChunks[i % workerNum].push(ports[i]); } for (let wn = 0; wn < workerNum; wn++) { const worker = new Worker('scannerWorker.js'); worker.onmessage = function (e) { const { foundPort: port } = e.data; if (port) { foundPort = port; terminateAllWorkers(); } totalScannedPorts++; if (portChunks[wn].length > 0) { worker.postMessage({ port: portChunks[wn].shift(), timeout: 120 }); } else { workerDone++; } }; workers.push(worker); if (portChunks[wn].length > 0) { worker.postMessage({ port: portChunks[wn].shift(), timeout: 120 }); } } // 确保在窗口关闭或刷新时终止所有 Web Worker window.addEventListener('beforeunload', terminateAllWorkers); } function startScan() { if (!AbortSignal || !AbortSignal.timeout) { alert("您的浏览器版本较低,检测结果可能不准确。"); } if (window.scanning) { alert("正在扫描中。"); } else { scanLocalhost(128); } } ================================================ FILE: service-worker.js ================================================ const CACHE_NAME = 'clash-scan-cache-v1'; const urlsToCache = [ '/ClashScan/', '/ClashScan/index.html', '/ClashScan/styles.css', '/ClashScan/script.js', '/ClashScan/scannerWorker.js', '/ClashScan/icons/icon-192x192.png', '/ClashScan/icons/icon-512x512.png' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => { return Promise.all( urlsToCache.map(url => { return cache.add(url).catch(error => { console.error(`Failed to cache ${url}:`, error); }); }) ); }) .catch(error => { console.error('Failed to open cache:', error); }) ); }); self.addEventListener('fetch', event => { const url = new URL(event.request.url); // 检查是否为 127.0.0.1 且端口不是 80 或 443 if (url.hostname === '127.0.0.1' && url.port !== '80' && url.port !== '443') { return; // 不缓存这些请求 } event.respondWith( caches.match(event.request) .then(response => { if (response) { return response; } return fetch(event.request); }) ); }); self.addEventListener('activate', event => { const cacheWhitelist = [CACHE_NAME]; event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); }) ); }); ================================================ FILE: styles.css ================================================ @import url("https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700"); html { background: #6C7989; background: #6C7989 linear-gradient(#6C7989, #434B55) fixed; height: 100% } body { padding: 50px 0; margin: 0; font: 14px/1.5 Lato, "Helvetica Neue", "PingFang SC", "Noto Sans SC", Helvetica, Arial, sans-serif; color: #555; font-weight: 300; min-height: calc(100% - 100px) } .wrapper { width: 800px; margin: 0 auto; background: #DEDEDE; border-radius: 8px; box-shadow: rgba(0, 0, 0, 0.2) 0 0 0 1px, rgba(0, 0, 0, 0.45) 0 3px 10px } header, section, footer { display: block } a { color: #069; text-decoration: none } p { margin: 0 0 20px; padding: 0 } strong { color: #222; font-weight: 700 } header { border-radius: 8px 8px 0 0; background: #C6EAFA; background: linear-gradient(#DDFBFC, #C6EAFA); position: relative; padding: 15px 20px; border-bottom: 1px solid #B2D2E1 } header h1 { margin: 0; padding: 0; font-size: 24px; line-height: 1.2; color: #069; text-shadow: rgba(255, 255, 255, 0.9) 0 1px 0 } header.without-description h1 { margin: 10px 0 } header p { margin: 0; color: #61778B; width: 300px; font-size: 13px } header p.view { display: none; font-weight: 700; text-shadow: rgba(255, 255, 255, 0.9) 0 1px 0; -webkit-font-smoothing: antialiased } header p.view a { color: #06c } header p.view small { font-weight: 400 } header ul { margin: 0; padding: 0; list-style: none; position: absolute; z-index: 1; right: 20px; top: 20px; height: 38px; padding: 1px 0; background: #5198DF; background: linear-gradient(#77B9FB, #3782CD); border-radius: 5px; box-shadow: inset rgba(255, 255, 255, 0.45) 0 1px 0, inset rgba(0, 0, 0, 0.2) 0 -1px 0; width: auto } header ul:before { content: ''; position: absolute; z-index: -1; left: -5px; top: -4px; right: -5px; bottom: -6px; background: rgba(0, 0, 0, 0.1); border-radius: 8px; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0, inset rgba(255, 255, 255, 0.7) 0 -1px 0 } header ul li { width: 79px; float: left; border-right: 1px solid #3A7CBE; height: 38px } header ul li.single { border: none } header ul li+li { width: 78px; border-left: 1px solid #8BBEF3 } header ul li+li+li { border-right: none; width: 79px } header ul a { line-height: 1; font-size: 11px; color: #fff; color: rgba(255, 255, 255, 0.8); display: block; text-align: center; font-weight: 400; padding-top: 6px; height: 40px; text-shadow: rgba(0, 0, 0, 0.4) 0 -1px 0 } header ul a strong { font-size: 14px; display: block; color: #fff; -webkit-font-smoothing: antialiased } section { padding: 15px 20px; font-size: 15px; border-top: 1px solid #fff; background: linear-gradient(#fafafa, #DEDEDE 700px); border-radius: 0 0 8px 8px; position: relative } h1, h2, h3, h4, h5, h6 { color: #222; padding: 0; margin: 0 0 20px; line-height: 1.2 } p, ul, ol, table, pre, dl { margin: 0 0 20px } h1, h2, h3 { line-height: 1.1 } h1 { font-size: 28px } h2 { color: #393939 } h3, h4, h5, h6 { color: #494949 } blockquote { margin: 0 -20px 20px; padding: 15px 20px 1px 40px; font-style: italic; background: #ccc; background: rgba(0, 0, 0, 0.06); color: #222 } img { max-width: 100% } code, pre { font-family: Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; color: #333; font-size: 12px; overflow-x: auto } pre { padding: 20px; background: #3A3C42; color: #f8f8f2; margin: 0 -20px 20px } pre code { color: #f8f8f2 } li pre { margin-left: -60px; padding-left: 60px } table { width: 100%; border-collapse: collapse } th, td { text-align: left; padding: 5px 10px; border-bottom: 1px solid #aaa } dt { color: #222; font-weight: 700 } th { color: #222 } small { font-size: 11px } hr { border: 0; background: #aaa; height: 1px; margin: 0 0 20px } kbd { background-color: #fafbfc; border: 1px solid #c6cbd1; border-bottom-color: #959da5; border-radius: 3px; box-shadow: inset 0 -1px 0 #959da5; color: #444d56; display: inline-block; font-size: 11px; line-height: 10px; padding: 3px 5px; vertical-align: middle } footer { width: 640px; margin: 0 auto; padding: 20px 0 0; color: #ccc; overflow: hidden } footer a { color: #fff; font-weight: bold } footer p { float: left } footer p+p { float: right } @media print, screen and (max-width: 800px) { body { padding: 0 } .wrapper { border-radius: 0; box-shadow: none; width: 100% } footer { border-radius: 0; padding: 20px; width: auto } footer p { float: none; margin: 0 } footer p+p { float: none } } @media print, screen and (max-width: 580px) { header ul { display: none } header p.view { display: block } header p { width: 100% } } .progress { display: block; height: 6px; background: #e6e6e6; width: 100% } .progress-done { display: block; height: 100%; background: #0000ff; width: 0%; }