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。
使用说明
- 推荐使用 Chrome ⩾ 103 版本(2022);
- 点击 “开始检测”,您将同意本网页进行端口扫描;
- 您本次的检测结果不会被上传至云端;
- 检测结果仅供参考。
检测结果
您正在浏览:
bilibili.com
bilibili.com
bilibili.com
================================================
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%;
}