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
================================================
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>在线 Clash 检测</title>
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" href="styles.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="wrapper">
<header>
<h1 id="title">您是否在使用 Clash?</h1>
<p>任何网站都能检测您是否正在使用 Clash。</p>
<ul>
<li style="width:120px"><a href="javascript:startScan()">Start Scan <strong>开始检测</strong></a></li>
<li><a href="https://github.com/MikeWang000000/ClashScan">View On <strong>GitHub</strong></a></li>
</ul>
</header>
<div class="progress"><div class="progress-done"></div></div>
<section>
<h2>使用说明</h2>
<ul>
<li>推荐使用 Chrome ⩾ 103 版本(2022);</li>
<li>点击 “开始检测”,您将同意本网页进行端口扫描;</li>
<li>您本次的检测结果不会被上传至云端;</li>
<li>检测结果仅供参考。</li>
</ul>
<hr />
<h2>检测结果</h2>
<div id="div_version">
<h3>Clash 版本:</h3>
<p id="txt_version">
暂无数据
</p>
</div>
<div id="div_hosts" style="display: none">
<h3>您正在浏览:</h3>
<blockquote>
<p id="txt_hosts">
bilibili.com<br />
bilibili.com<br />
bilibili.com<br />
</p>
</blockquote>
</div>
<div id="div_servers" style="display: none">
<h3>您的服务器:</h3>
<pre class=""><code id="txt_servers"></code></pre>
</div>
<hr />
</section>
</div>
<footer>
<p>Project maintained by <a href="https://github.com/MikeWang000000">MikeWang000000</a></p>
<p>Hosted on GitHub Pages — Theme by <a href="https://github.com/orderedlist">orderedlist</a></p>
</footer>
<script src="script.js"></script>
</body>
</html>
================================================
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("<br/>");
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%;
}
gitextract_n6tev4bh/ ├── index.html ├── manifest.json ├── scannerWorker.js ├── script.js ├── service-worker.js └── styles.css
SYMBOL INDEX (15 symbols across 3 files)
FILE: scannerWorker.js
function checkIsClash (line 9) | async function checkIsClash(port) {
FILE: script.js
function avg (line 21) | function avg (arr) {
function shuffle (line 27) | function shuffle(array) {
function randArr (line 35) | function randArr(start, end, length) {
function range (line 39) | function range(start, end) {
function sleep (line 43) | function sleep(ms) {
function portTime (line 47) | async function portTime(port) {
function guessOpenProxyPort (line 59) | async function guessOpenProxyPort() {
function guessClashVersion (line 84) | async function guessClashVersion(port) {
function getClashVersion (line 129) | async function getClashVersion(port) {
function getClashTraffic (line 151) | async function getClashTraffic(port) {
function getClashProxies (line 176) | async function getClashProxies(port) {
function scanLocalhost (line 191) | async function scanLocalhost(workerNum) {
function startScan (line 316) | function startScan() {
FILE: service-worker.js
constant CACHE_NAME (line 1) | const CACHE_NAME = 'clash-scan-cache-v1';
Condensed preview — 6 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (23K chars).
[
{
"path": "index.html",
"chars": 2127,
"preview": "<!doctype html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"utf-8\">\n <title>在线 Clash 检测</title>\n <link rel=\"man"
},
{
"path": "manifest.json",
"chars": 467,
"preview": "{\n \"name\": \"Clash Scan\",\n \"short_name\": \"ClashScan\",\n \"start_url\": \"ClashScan/index.html\",\n \"display\": \"stan"
},
{
"path": "scannerWorker.js",
"chars": 1074,
"preview": "// scannerWorker.js\nself.onmessage = async function (e) {\n const { port, timeout } = e.data;\n let foundPort = 0;\n\n"
},
{
"path": "script.js",
"chars": 10291,
"preview": "if ('serviceWorker' in navigator) {\n window.addEventListener('load', () => {\n navigator.serviceWorker.register('"
},
{
"path": "service-worker.js",
"chars": 1495,
"preview": "const CACHE_NAME = 'clash-scan-cache-v1';\nconst urlsToCache = [\n '/ClashScan/',\n '/ClashScan/index.html',\n '/ClashSca"
},
{
"path": "styles.css",
"chars": 5534,
"preview": "@import url(\"https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700\");\n\nhtml {\n background: #6C7989"
}
]
About this extraction
This page contains the full source code of the MikeWang000000/ClashScan GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 6 files (20.5 KB), approximately 6.0k tokens, and a symbol index with 15 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.