mtproto-checker 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -4
- package/check.js +239 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,8 +42,36 @@ Optional, if you want the `check-proxies` command available locally:
|
|
|
42
42
|
npm link
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
### GitHub Packages
|
|
46
|
+
|
|
47
|
+
This project can also be published to GitHub Packages as
|
|
48
|
+
`@tar4s/mtproto-checker`. GitHub's npm registry requires scoped package names,
|
|
49
|
+
so the GitHub Packages workflow applies that scoped name during publishing.
|
|
50
|
+
|
|
51
|
+
To install from GitHub Packages:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm config set @tar4s:registry https://npm.pkg.github.com
|
|
55
|
+
npm install @tar4s/mtproto-checker
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Private packages require authentication with a GitHub personal access token that
|
|
59
|
+
has `read:packages`.
|
|
60
|
+
|
|
45
61
|
## Quick Start
|
|
46
62
|
|
|
63
|
+
Start the HTTP API server:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
TG_API_ID=12345 \
|
|
67
|
+
TG_API_HASH=abcdef \
|
|
68
|
+
CHECK_AUTH_USER=admin \
|
|
69
|
+
CHECK_AUTH_PASSWORD=secret \
|
|
70
|
+
node check.js
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
By default it listens on port `3080`. Override it with `PORT`.
|
|
74
|
+
|
|
47
75
|
Check URLs listed in `urls.txt`:
|
|
48
76
|
|
|
49
77
|
```bash
|
|
@@ -62,11 +90,25 @@ With `npm link`:
|
|
|
62
90
|
TG_API_ID=12345 TG_API_HASH=abcdef check-proxies --sources urls.txt
|
|
63
91
|
```
|
|
64
92
|
|
|
93
|
+
Check one proxy link directly:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
TG_API_ID=12345 TG_API_HASH=abcdef node check.js \
|
|
97
|
+
--proxy 'tg://proxy?server=quackton.life&port=443&secret=7mX8dVOh9cqLULccAVs4ciR5YW5kZXgucnU'
|
|
98
|
+
```
|
|
99
|
+
|
|
65
100
|
## Input Sources
|
|
66
101
|
|
|
67
102
|
You can provide proxies in several ways.
|
|
68
103
|
|
|
69
|
-
### 1.
|
|
104
|
+
### 1. Direct Proxy Link
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
TG_API_ID=12345 TG_API_HASH=abcdef node check.js \
|
|
108
|
+
--proxy 'https://t.me/proxy?server=1.2.3.4&port=443&secret=...'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 2. Source URL File
|
|
70
112
|
|
|
71
113
|
`urls.txt` contains one remote text-list URL per line:
|
|
72
114
|
|
|
@@ -81,7 +123,7 @@ Run:
|
|
|
81
123
|
TG_API_ID=12345 TG_API_HASH=abcdef node check.js --sources urls.txt
|
|
82
124
|
```
|
|
83
125
|
|
|
84
|
-
###
|
|
126
|
+
### 3. One Or More Remote URLs
|
|
85
127
|
|
|
86
128
|
```bash
|
|
87
129
|
TG_API_ID=12345 TG_API_HASH=abcdef node check.js \
|
|
@@ -95,13 +137,13 @@ Positional HTTP URLs also work:
|
|
|
95
137
|
TG_API_ID=12345 TG_API_HASH=abcdef node check.js https://example.com/proxies.txt
|
|
96
138
|
```
|
|
97
139
|
|
|
98
|
-
###
|
|
140
|
+
### 4. Local Proxy File
|
|
99
141
|
|
|
100
142
|
```bash
|
|
101
143
|
TG_API_ID=12345 TG_API_HASH=abcdef node check.js proxies.txt
|
|
102
144
|
```
|
|
103
145
|
|
|
104
|
-
###
|
|
146
|
+
### 5. stdin
|
|
105
147
|
|
|
106
148
|
```bash
|
|
107
149
|
cat proxies.txt | TG_API_ID=12345 TG_API_HASH=abcdef node check.js
|
|
@@ -114,6 +156,7 @@ Input files may contain blank lines and `#` comments.
|
|
|
114
156
|
| Option | Default | Description |
|
|
115
157
|
| --- | ---: | --- |
|
|
116
158
|
| `--url <url>` | none | Add a remote proxy-list URL. Can be repeated. |
|
|
159
|
+
| `--proxy <link>` | none | Check one `tg://proxy` or `https://t.me/proxy` link directly. |
|
|
117
160
|
| `--sources <file>` | none | Read remote source URLs from a file, one URL per line. |
|
|
118
161
|
| `--dc <1-5>` | `2` | Telegram data center ID used for `testProxy`. |
|
|
119
162
|
| `--timeout <sec>` | `10` | Per-proxy TDLib timeout in seconds. Decimals are allowed. |
|
|
@@ -127,6 +170,64 @@ Environment variables:
|
|
|
127
170
|
| --- | --- | --- |
|
|
128
171
|
| `TG_API_ID` | yes | Telegram API ID from `my.telegram.org`. |
|
|
129
172
|
| `TG_API_HASH` | yes | Telegram API hash from `my.telegram.org`. |
|
|
173
|
+
| `CHECK_AUTH_USER` | HTTP server only | Basic auth username. |
|
|
174
|
+
| `CHECK_AUTH_PASSWORD` | HTTP server only | Basic auth password. |
|
|
175
|
+
| `PORT` | no | HTTP server port. Defaults to `3080`. |
|
|
176
|
+
|
|
177
|
+
## HTTP API
|
|
178
|
+
|
|
179
|
+
Running `node check.js` without CLI arguments starts the HTTP server. The server
|
|
180
|
+
requires Basic auth and exposes one endpoint:
|
|
181
|
+
|
|
182
|
+
```http
|
|
183
|
+
POST /check
|
|
184
|
+
Content-Type: application/json
|
|
185
|
+
Authorization: Basic ...
|
|
186
|
+
|
|
187
|
+
{ "url": "https://example.com/proxies.txt" }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The `url` field accepts either a remote `http(s)` proxy list or one direct
|
|
191
|
+
`tg://proxy` / `https://t.me/proxy` link.
|
|
192
|
+
|
|
193
|
+
Example:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
curl -u admin:secret \
|
|
197
|
+
-H 'content-type: application/json' \
|
|
198
|
+
-d '{"url":"https://example.com/proxies.txt"}' \
|
|
199
|
+
http://127.0.0.1:3080/check
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Direct proxy link example:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
curl -u admin:secret \
|
|
206
|
+
-H 'content-type: application/json' \
|
|
207
|
+
-d '{"url":"tg://proxy?server=quackton.life&port=443&secret=7mX8dVOh9cqLULccAVs4ciR5YW5kZXgucnU"}' \
|
|
208
|
+
http://127.0.0.1:3080/check
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Response:
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"url": "https://example.com/proxies.txt",
|
|
216
|
+
"count": 1,
|
|
217
|
+
"working": 1,
|
|
218
|
+
"results": [
|
|
219
|
+
{
|
|
220
|
+
"server": "1.2.3.4",
|
|
221
|
+
"port": 443,
|
|
222
|
+
"sni": "example.com",
|
|
223
|
+
"ok": true,
|
|
224
|
+
"ms": 841,
|
|
225
|
+
"error": null,
|
|
226
|
+
"link": "tg://proxy?server=1.2.3.4&port=443&secret=..."
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
```
|
|
130
231
|
|
|
131
232
|
## Output
|
|
132
233
|
|
package/check.js
CHANGED
|
@@ -18,15 +18,19 @@
|
|
|
18
18
|
* result de-duplicated (by server+port+secret) via a Set.
|
|
19
19
|
*
|
|
20
20
|
* CLI usage:
|
|
21
|
+
* TG_API_ID=12345 TG_API_HASH=abcdef CHECK_AUTH_USER=admin CHECK_AUTH_PASSWORD=secret node check.js
|
|
22
|
+
* # starts the HTTP API server on PORT (default 3080)
|
|
21
23
|
* TG_API_ID=12345 TG_API_HASH=abcdef... node check.js [sources] [options]
|
|
22
24
|
* # sources: any positional http(s) URL, a local file path, or stdin
|
|
23
25
|
* node check.js https://raw.githubusercontent.com/u/r/main/list.txt
|
|
24
26
|
* node check.js --url URL1 --url URL2
|
|
27
|
+
* node check.js --proxy "tg://proxy?server=...&port=443&secret=..."
|
|
25
28
|
* node check.js --sources urls.txt # file with one URL per line
|
|
26
29
|
* cat proxies.txt | node check.js
|
|
27
30
|
*
|
|
28
31
|
* Options:
|
|
29
32
|
* --url <url> add a source URL (repeatable)
|
|
33
|
+
* --proxy <link> check one proxy link directly
|
|
30
34
|
* --sources <file> file containing source URLs (one per line, # comments ok)
|
|
31
35
|
* --dc <1-5> data center id to test against (default 2)
|
|
32
36
|
* --timeout <sec> per-proxy TDLib timeout in seconds (default 10)
|
|
@@ -43,7 +47,9 @@
|
|
|
43
47
|
*/
|
|
44
48
|
|
|
45
49
|
const fs = require('fs')
|
|
50
|
+
const http = require('http')
|
|
46
51
|
const path = require('path')
|
|
52
|
+
const crypto = require('crypto')
|
|
47
53
|
const tdl = require('tdl')
|
|
48
54
|
const { getTdjson } = require('prebuilt-tdlib')
|
|
49
55
|
|
|
@@ -65,10 +71,10 @@ function configureTdlibOnce(state = tdlibConfigState, configure = tdl.configure,
|
|
|
65
71
|
/**
|
|
66
72
|
* Parse argv into an options object, collecting source URLs and/or a file path.
|
|
67
73
|
* @param {string[]} argv - process.argv.slice(2)
|
|
68
|
-
* @returns {{file: string|null, urls: string[], sourcesFile: string|null, dc: number, timeout: number, concurrency: number, out: string, iterations: number}}
|
|
74
|
+
* @returns {{file: string|null, urls: string[], proxy: string|null, sourcesFile: string|null, dc: number, timeout: number, concurrency: number, out: string, iterations: number}}
|
|
69
75
|
*/
|
|
70
76
|
function parseArgs(argv) {
|
|
71
|
-
const opts = { file: null, urls: [], sourcesFile: null, dc: 2, timeout: 10, concurrency: 30, out: 'result', iterations: 1 }
|
|
77
|
+
const opts = { file: null, urls: [], proxy: null, sourcesFile: null, dc: 2, timeout: 10, concurrency: 30, out: 'result', iterations: 1 }
|
|
72
78
|
for (let i = 0; i < argv.length; i++) {
|
|
73
79
|
const a = argv[i]
|
|
74
80
|
if (a === '--dc') opts.dc = parseInt(argv[++i], 10)
|
|
@@ -77,6 +83,7 @@ function parseArgs(argv) {
|
|
|
77
83
|
else if (a === '--out') opts.out = argv[++i]
|
|
78
84
|
else if (a === '--iterations') opts.iterations = parseInt(argv[++i], 10)
|
|
79
85
|
else if (a === '--url') opts.urls.push(argv[++i])
|
|
86
|
+
else if (a === '--proxy') opts.proxy = argv[++i]
|
|
80
87
|
else if (a === '--sources') opts.sourcesFile = argv[++i]
|
|
81
88
|
else if (/^https?:\/\//i.test(a)) opts.urls.push(a)
|
|
82
89
|
else if (!a.startsWith('--')) opts.file = a
|
|
@@ -384,6 +391,233 @@ async function checkProxiesFromUrls(urls, opts) {
|
|
|
384
391
|
return checkProxies(proxies, opts)
|
|
385
392
|
}
|
|
386
393
|
|
|
394
|
+
async function checkSingleUrl(url, opts) {
|
|
395
|
+
const directProxy = parseLink(url)
|
|
396
|
+
if (directProxy) {
|
|
397
|
+
const checker = opts.checker || checkProxies
|
|
398
|
+
return checker([directProxy], opts)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let parsed
|
|
402
|
+
try {
|
|
403
|
+
parsed = new URL(url)
|
|
404
|
+
} catch {
|
|
405
|
+
throw new Error('url must be a proxy link or an http or https URL')
|
|
406
|
+
}
|
|
407
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
408
|
+
throw new Error('url must be a proxy link or an http or https URL')
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const fetcher = opts.fetcher || fetchText
|
|
412
|
+
const checker = opts.checker || checkProxies
|
|
413
|
+
const text = await fetcher(url)
|
|
414
|
+
const proxies = mergeProxies([text])
|
|
415
|
+
return checker(proxies, opts)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async function resolveInputProxies(opts, deps = {}) {
|
|
419
|
+
const readFile = deps.readFile || (file => fs.readFileSync(file, 'utf8'))
|
|
420
|
+
const readInputFn = deps.readInput || readInput
|
|
421
|
+
const loadFromUrls = deps.loadFromUrls || loadProxiesFromUrls
|
|
422
|
+
|
|
423
|
+
if (opts.proxy) {
|
|
424
|
+
const proxy = parseLink(opts.proxy)
|
|
425
|
+
return proxy ? [proxy] : []
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (opts.sourcesFile) {
|
|
429
|
+
const text = readFile(opts.sourcesFile)
|
|
430
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
431
|
+
const line = rawLine.split('#')[0].trim()
|
|
432
|
+
if (line) opts.urls.push(line)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (opts.urls.length > 0) {
|
|
437
|
+
console.error(`Fetching ${opts.urls.length} source URL(s)...`)
|
|
438
|
+
return loadFromUrls(opts.urls)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const input = await readInputFn(opts.file)
|
|
442
|
+
return mergeProxies([input])
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function toReport(results) {
|
|
446
|
+
return results.map(c => ({
|
|
447
|
+
server: c.proxy ? c.proxy.server : c.server,
|
|
448
|
+
port: c.proxy ? c.proxy.port : c.port,
|
|
449
|
+
sni: c.proxy ? c.proxy.sni : c.sni,
|
|
450
|
+
ok: c.ok,
|
|
451
|
+
ms: c.ms,
|
|
452
|
+
error: c.error,
|
|
453
|
+
link: c.proxy ? c.proxy.raw : c.link
|
|
454
|
+
}))
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function jsonResponse(res, statusCode, body, headers = {}) {
|
|
458
|
+
const payload = JSON.stringify(body, null, 2)
|
|
459
|
+
res.writeHead(statusCode, {
|
|
460
|
+
'content-type': 'application/json; charset=utf-8',
|
|
461
|
+
'content-length': Buffer.byteLength(payload),
|
|
462
|
+
...headers
|
|
463
|
+
})
|
|
464
|
+
res.end(payload)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function safeEqual(a, b) {
|
|
468
|
+
const left = Buffer.from(a)
|
|
469
|
+
const right = Buffer.from(b)
|
|
470
|
+
return left.length === right.length && crypto.timingSafeEqual(left, right)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function isAuthorized(req, auth) {
|
|
474
|
+
const header = req.headers.authorization
|
|
475
|
+
if (!header || !header.startsWith('Basic ')) return false
|
|
476
|
+
|
|
477
|
+
let decoded
|
|
478
|
+
try {
|
|
479
|
+
decoded = Buffer.from(header.slice(6), 'base64').toString('utf8')
|
|
480
|
+
} catch {
|
|
481
|
+
return false
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const separator = decoded.indexOf(':')
|
|
485
|
+
if (separator === -1) return false
|
|
486
|
+
const user = decoded.slice(0, separator)
|
|
487
|
+
const password = decoded.slice(separator + 1)
|
|
488
|
+
return safeEqual(user, auth.user) && safeEqual(password, auth.password)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function readJsonBody(req, limitBytes = 1024 * 1024) {
|
|
492
|
+
return new Promise((resolve, reject) => {
|
|
493
|
+
let size = 0
|
|
494
|
+
let raw = ''
|
|
495
|
+
req.setEncoding('utf8')
|
|
496
|
+
req.on('data', chunk => {
|
|
497
|
+
size += Buffer.byteLength(chunk)
|
|
498
|
+
if (size > limitBytes) {
|
|
499
|
+
reject(Object.assign(new Error('Request body too large'), { statusCode: 413 }))
|
|
500
|
+
req.destroy()
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
raw += chunk
|
|
504
|
+
})
|
|
505
|
+
req.on('end', () => {
|
|
506
|
+
try {
|
|
507
|
+
resolve(raw ? JSON.parse(raw) : {})
|
|
508
|
+
} catch {
|
|
509
|
+
reject(Object.assign(new Error('Invalid JSON body'), { statusCode: 400 }))
|
|
510
|
+
}
|
|
511
|
+
})
|
|
512
|
+
req.on('error', reject)
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function logRequest(logger, req, statusCode, started) {
|
|
517
|
+
const ms = Date.now() - started
|
|
518
|
+
const forwarded = req.headers['x-forwarded-for']
|
|
519
|
+
const remote = Array.isArray(forwarded) ? forwarded[0] : forwarded || req.socket.remoteAddress || '-'
|
|
520
|
+
logger(`${req.id} ${remote} ${req.method} ${req.url} ${statusCode} ${ms}ms`)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function createServer({ auth, checkUrl, logger = console.error }) {
|
|
524
|
+
let nextRequestId = 0
|
|
525
|
+
const realm = 'Basic realm="mtproto-checker"'
|
|
526
|
+
|
|
527
|
+
return http.createServer(async (req, res) => {
|
|
528
|
+
const started = Date.now()
|
|
529
|
+
req.id = `req-${++nextRequestId}`
|
|
530
|
+
let statusCode = 500
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
if (!isAuthorized(req, auth)) {
|
|
534
|
+
statusCode = 401
|
|
535
|
+
jsonResponse(res, statusCode, { error: 'Unauthorized' }, { 'www-authenticate': realm })
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (req.url !== '/check') {
|
|
540
|
+
statusCode = 404
|
|
541
|
+
jsonResponse(res, statusCode, { error: 'Not found' })
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (req.method !== 'POST') {
|
|
546
|
+
statusCode = 405
|
|
547
|
+
jsonResponse(res, statusCode, { error: 'Method not allowed' }, { allow: 'POST' })
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const body = await readJsonBody(req)
|
|
552
|
+
if (!body || typeof body.url !== 'string' || body.url.trim() === '') {
|
|
553
|
+
statusCode = 400
|
|
554
|
+
jsonResponse(res, statusCode, { error: 'Request body must include url' })
|
|
555
|
+
return
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const url = body.url.trim()
|
|
559
|
+
let results
|
|
560
|
+
try {
|
|
561
|
+
results = await checkUrl(url)
|
|
562
|
+
} catch (err) {
|
|
563
|
+
statusCode = 502
|
|
564
|
+
jsonResponse(res, statusCode, {
|
|
565
|
+
error: 'Failed to check url',
|
|
566
|
+
detail: err.message || String(err)
|
|
567
|
+
})
|
|
568
|
+
return
|
|
569
|
+
}
|
|
570
|
+
const report = toReport(results)
|
|
571
|
+
statusCode = 200
|
|
572
|
+
jsonResponse(res, statusCode, {
|
|
573
|
+
url,
|
|
574
|
+
count: report.length,
|
|
575
|
+
working: report.filter(item => item.ok).length,
|
|
576
|
+
results: report
|
|
577
|
+
})
|
|
578
|
+
} catch (err) {
|
|
579
|
+
statusCode = err.statusCode || 500
|
|
580
|
+
const message = statusCode === 500 ? 'Internal server error' : err.message
|
|
581
|
+
jsonResponse(res, statusCode, { error: message })
|
|
582
|
+
if (statusCode === 500) logger(`${req.id} error ${err.stack || err.message || err}`)
|
|
583
|
+
} finally {
|
|
584
|
+
logRequest(logger, req, statusCode, started)
|
|
585
|
+
}
|
|
586
|
+
})
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function shouldStartServer(argv) {
|
|
590
|
+
return argv.length === 0
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async function startServer(env = process.env) {
|
|
594
|
+
const apiId = parseInt(env.TG_API_ID, 10)
|
|
595
|
+
const apiHash = env.TG_API_HASH
|
|
596
|
+
const user = env.CHECK_AUTH_USER
|
|
597
|
+
const password = env.CHECK_AUTH_PASSWORD
|
|
598
|
+
const port = parseInt(env.PORT || '3080', 10)
|
|
599
|
+
|
|
600
|
+
if (!apiId || !apiHash) throw new Error('Set TG_API_ID and TG_API_HASH (get them at https://my.telegram.org).')
|
|
601
|
+
if (!user || !password) throw new Error('Set CHECK_AUTH_USER and CHECK_AUTH_PASSWORD for HTTP Basic auth.')
|
|
602
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error('PORT must be a valid TCP port.')
|
|
603
|
+
|
|
604
|
+
const server = createServer({
|
|
605
|
+
auth: { user, password },
|
|
606
|
+
checkUrl: async url => checkSingleUrl(url, { apiId, apiHash })
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
await new Promise((resolve, reject) => {
|
|
610
|
+
server.once('error', reject)
|
|
611
|
+
server.listen(port, () => {
|
|
612
|
+
server.off('error', reject)
|
|
613
|
+
resolve()
|
|
614
|
+
})
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
console.error(`MTProto checker HTTP server listening on :${port}`)
|
|
618
|
+
return server
|
|
619
|
+
}
|
|
620
|
+
|
|
387
621
|
/**
|
|
388
622
|
* CLI entry point: resolve sources (URLs / file / stdin), check, and write
|
|
389
623
|
* `<out>.json` (full report) and `<out>.txt` (working links, fastest first).
|
|
@@ -398,23 +632,7 @@ async function main() {
|
|
|
398
632
|
process.exit(1)
|
|
399
633
|
}
|
|
400
634
|
|
|
401
|
-
|
|
402
|
-
if (opts.sourcesFile) {
|
|
403
|
-
const text = fs.readFileSync(opts.sourcesFile, 'utf8')
|
|
404
|
-
for (const rawLine of text.split(/\r?\n/)) {
|
|
405
|
-
const line = rawLine.split('#')[0].trim()
|
|
406
|
-
if (line) opts.urls.push(line)
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
let proxies
|
|
411
|
-
if (opts.urls.length > 0) {
|
|
412
|
-
console.error(`Fetching ${opts.urls.length} source URL(s)...`)
|
|
413
|
-
proxies = await loadProxiesFromUrls(opts.urls)
|
|
414
|
-
} else {
|
|
415
|
-
const input = await readInput(opts.file)
|
|
416
|
-
proxies = mergeProxies([input])
|
|
417
|
-
}
|
|
635
|
+
const proxies = await resolveInputProxies(opts)
|
|
418
636
|
|
|
419
637
|
if (proxies.length === 0) {
|
|
420
638
|
console.error('No valid tg://proxy or t.me/proxy links found.')
|
|
@@ -463,9 +681,9 @@ async function main() {
|
|
|
463
681
|
process.exit(0)
|
|
464
682
|
}
|
|
465
683
|
|
|
466
|
-
module.exports = { configureTdlibOnce, parseArgs, checkProxiesFromUrls, loadProxiesFromUrls, checkProxies, runIterativeChecks, mergeProxies, parseLink, normalizeSecret, faketlsSni }
|
|
684
|
+
module.exports = { checkSingleUrl, configureTdlibOnce, createServer, parseArgs, resolveInputProxies, checkProxiesFromUrls, loadProxiesFromUrls, checkProxies, runIterativeChecks, mergeProxies, parseLink, normalizeSecret, faketlsSni, shouldStartServer, startServer }
|
|
467
685
|
|
|
468
|
-
if (require.main === module) main().catch(err => {
|
|
686
|
+
if (require.main === module) (shouldStartServer(process.argv.slice(2)) ? startServer() : main()).catch(err => {
|
|
469
687
|
console.error(err)
|
|
470
688
|
process.exit(1)
|
|
471
689
|
})
|