11import { spawn , type ChildProcess } from 'node:child_process' ;
22import fs from 'node:fs' ;
3+ import net from 'node:net' ;
34import path from 'node:path' ;
45
56import yaml from 'js-yaml' ;
@@ -15,6 +16,9 @@ const API_PORT = 19090;
1516const API_BASE = `http://127.0.0.1:${ API_PORT } ` ;
1617const TEST_URL = 'http://www.gstatic.com/generate_204' ;
1718
19+ const TCP_TIMEOUT = 8000 ;
20+ const TCP_CONCURRENCY = 50 ;
21+
1822function findMihomoBinary ( ) : string {
1923 const candidates = [
2024 path . join ( ROOT , 'mihomo' ) ,
@@ -211,6 +215,34 @@ function extractTags(name: string): string {
211215 return `${ speed } ${ multStr } ${ lossStr } ` ;
212216}
213217
218+ function tcpCheck ( proxy : Proxy ) : Promise < boolean > {
219+ return new Promise ( ( resolve ) => {
220+ const socket = net . createConnection ( { host : proxy . server , port : proxy . port , timeout : TCP_TIMEOUT } ) ;
221+ socket . once ( 'connect' , ( ) => { socket . destroy ( ) ; resolve ( true ) ; } ) ;
222+ socket . once ( 'timeout' , ( ) => { socket . destroy ( ) ; resolve ( false ) ; } ) ;
223+ socket . once ( 'error' , ( ) => { socket . destroy ( ) ; resolve ( false ) ; } ) ;
224+ } ) ;
225+ }
226+
227+ async function tcpFilter ( proxies : Proxy [ ] ) : Promise < Proxy [ ] > {
228+ const alive : Proxy [ ] = [ ] ;
229+ let tested = 0 ;
230+
231+ for ( let i = 0 ; i < proxies . length ; i += TCP_CONCURRENCY ) {
232+ const batch = proxies . slice ( i , i + TCP_CONCURRENCY ) ;
233+ const results = await Promise . all ( batch . map ( async ( p ) => ( { proxy : p , ok : await tcpCheck ( p ) } ) ) ) ;
234+ for ( const r of results ) {
235+ if ( r . ok ) alive . push ( r . proxy ) ;
236+ }
237+ tested += batch . length ;
238+ const pct = Math . round ( ( tested / proxies . length ) * 100 ) ;
239+ process . stdout . write ( `\r TCP 测试: ${ tested } /${ proxies . length } (${ pct } %) 存活: ${ alive . length } ` ) ;
240+ }
241+ process . stdout . write ( '\n' ) ;
242+ console . log ( ` TCP 连通率: ${ alive . length } /${ proxies . length } (${ Math . round ( ( alive . length / proxies . length ) * 100 ) } %)` ) ;
243+ return alive ;
244+ }
245+
214246function topByCountry ( proxies : Proxy [ ] ) : Proxy [ ] {
215247 const groups = new Map < string , Proxy [ ] > ( ) ;
216248 for ( const p of proxies ) {
@@ -247,16 +279,17 @@ async function main() {
247279 { name : '精选' , file : 'curated-raw.yaml' } ,
248280 ] ;
249281
250- console . log ( `\n跳过测速,仅精选 Top 筛选\n` ) ;
282+ console . log ( `\nTCP 连通性测试 + 精选 Top 筛选\n` ) ;
251283
252284 for ( const cat of categories ) {
253285 const filePath = path . join ( DATA_DIR , cat . file ) ;
254286 let proxies = readYaml < { proxies : Proxy [ ] } > ( filePath ) . proxies ;
255287 console . log ( `${ cat . name } 节点 (${ proxies . length } ):` ) ;
256288 if ( cat . file === 'curated-raw.yaml' ) {
289+ proxies = await tcpFilter ( proxies ) ;
257290 const before = proxies . length ;
258291 proxies = topByCountry ( proxies ) ;
259- console . log ( ` Top 筛选: ${ proxies . length } /${ before } (HK/TW 最多 ${ CURATED_LIMITS [ 'HK' ] } ,其他 ${ CURATED_DEFAULT_LIMIT } ) ` ) ;
292+ console . log ( ` Top 筛选: ${ proxies . length } /${ before } ` ) ;
260293 }
261294 writeYaml ( filePath , { proxies } ) ;
262295 }
0 commit comments