Enhance MTProxy checker by introducing a new -probe mode deep-strict for stricter response validation. Update README and Docker documentation to clarify probing modes, exit codes, and the new MTPROXY_TDLIB_HELPER and MTPROXY_TDLIB_TIMEOUT environment variables for external helper integration. Refactor connection handling and error reporting to improve robustness and clarity in response verification.
Publish mtproxy_checker Docker image / test (push) Successful in 7s
Publish mtproxy_checker Docker image / build-and-push (push) Successful in 53s

This commit is contained in:
Denozordec
2026-04-11 16:12:45 +07:00
parent 24a7315a16
commit 388784eb5e
17 changed files with 902 additions and 24 deletions
+40
View File
@@ -0,0 +1,40 @@
# TDLib helper для `mtproxy_checkerd` (Node.js)
> Предпочтительный вариант — нативный **`tdlib_ping`** в корне репозитория (`cmd/tdlib_ping`, `libtdjson`, официальный JSON API TDLib). Этот каталог остаётся как **legacy** на Node + `prebuilt-tdlib`, если так удобнее в образе.
Внешний процесс: **TDLib** (`addProxy` + `pingProxy`), как в [telegram-mtproto-proxy-checker](https://github.com/AmirTahaMim/telegram-mtproto-proxy-checker). В stdout печатается **одна строка JSON** — её парсит `mtproxy_checkerd`.
## Установка
```bash
cd contrib/tdlib-ping
npm install
```
## Запуск вручную
```bash
node ping.js 'tg://proxy?server=HOST&port=PORT&secret=HEX'
```
Успех: строка `{"ok":true,"error":"","exit_code":0}` и код выхода `0`.
Ошибка секрета: `exit_code` `1`.
Прочие ошибки: `exit_code` `2` (или `4` при таймауте внутри скрипта).
## Docker
Базовый образ `mtproxy_checker` остаётся без Node/TDLib. Соберите свой слой: установите Node 18+, скопируйте `contrib/tdlib-ping`, выполните `npm install`, задайте:
```text
MTPROXY_TDLIB_HELPER=/usr/local/bin/mtproxy-tdlib-ping
MTPROXY_TDLIB_TIMEOUT=45s
```
Обёртка-скрипт `mtproxy-tdlib-ping`:
```sh
#!/bin/sh
exec node /opt/tdlib-ping/ping.js "$1"
```
TDLib проверка выполняется только при **`MTPROXY_PROBE=fast`** (или пусто).
+15
View File
@@ -0,0 +1,15 @@
{
"name": "mtproxy-checker-tdlib-ping",
"version": "1.0.0",
"private": true,
"description": "TDLib addProxy+pingProxy helper for mtproxy_checkerd (stdout: one JSON line)",
"main": "ping.js",
"engines": {
"node": ">=18.0.0"
},
"dependencies": {
"prebuilt-tdlib": "^0.1008059.0",
"tdl": "^7.4.1",
"tdl-tdlib-addon": "^1.2.2"
}
}
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env node
/**
* TDLib addProxy + pingProxy; prints one JSON line to stdout for mtproxy_checkerd.
* Based on flow from https://github.com/AmirTahaMim/telegram-mtproto-proxy-checker (MIT).
*/
'use strict';
const path = require('path');
const os = require('os');
const { Client } = require('tdl');
const { TDLib } = require('tdl-tdlib-addon');
const tdl = require('tdl');
try {
const { getTdjson } = require('prebuilt-tdlib');
tdl.configure({ tdjson: getTdjson() });
} catch (_) {
/* system tdjson */
}
function outJson(ok, error, exitCode) {
console.log(JSON.stringify({ ok, error: error || '', exit_code: exitCode }));
}
function parseProxyUrl(url) {
const tgPattern = /^tg:\/\/proxy\?/;
const httpsPattern = /^https?:\/\/(www\.)?t\.me\/proxy\?/;
if (!tgPattern.test(url) && !httpsPattern.test(url)) {
throw new Error('Invalid proxy URL format');
}
const params = new URLSearchParams(url.split('?')[1]);
const server = params.get('server');
const port = parseInt(params.get('port'), 10);
const secret = params.get('secret');
if (!server || !port || !secret) {
throw new Error('Missing server, port, or secret');
}
if (isNaN(port) || port < 1 || port > 65535) {
throw new Error('Invalid port');
}
return { server, port, secret };
}
function normalizeSecret(secret) {
const hexPattern = /^[0-9a-fA-F]+$/;
if (hexPattern.test(secret)) {
if (secret.length % 2 !== 0) throw new Error('INVALID_SECRET');
return Buffer.from(secret, 'hex').toString('hex').toLowerCase();
}
let normalized = secret.replace(/-/g, '+').replace(/_/g, '/');
const padding = normalized.length % 4;
if (padding !== 0) normalized += '='.repeat(4 - padding);
return Buffer.from(normalized, 'base64').toString('hex').toLowerCase();
}
function extractErrorMessage(error) {
if (error.response && error.response._ === 'error') {
return `Error ${error.response.code}: ${error.response.message || ''}`;
}
if (error.message) return error.message;
return String(error);
}
async function verifyProxy(server, port, hexSecret, timeoutMs) {
const base = path.join(os.tmpdir(), `mtproxy_tdlib_${process.pid}_${Date.now()}`);
const tdlib = new TDLib();
const apiId = parseInt(process.env.MTPROXY_TD_API_ID || '12345', 10);
const apiHash = process.env.MTPROXY_TD_API_HASH || '0123456789abcdef0123456789abcdef';
const client = new Client(tdlib, {
apiId,
apiHash,
useTestDc: false,
databaseDirectory: path.join(base, 'db'),
filesDirectory: path.join(base, 'files'),
});
try {
try {
await client.connect();
} catch (e) {
return { ok: false, error: extractErrorMessage(e), code: 2 };
}
let addProxyResult;
try {
addProxyResult = await client.invoke({
_: 'addProxy',
server,
port,
enable: true,
type: { _: 'proxyTypeMtproto', secret: hexSecret },
});
} catch (error) {
const msg = extractErrorMessage(error);
if (msg.includes('INVALID_SECRET') || /secret/i.test(msg)) {
return { ok: false, error: 'INVALID_SECRET', code: 1 };
}
return { ok: false, error: msg, code: 2 };
}
if (addProxyResult._ !== 'proxy') {
return { ok: false, error: 'addProxy did not return proxy', code: 2 };
}
const proxyId = addProxyResult.id;
const pingPromise = client.invoke({ _: 'pingProxy', proxy_id: proxyId });
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('TIMEOUT')), timeoutMs)
);
try {
await Promise.race([pingPromise, timeoutPromise]);
return { ok: true, error: '', code: 0 };
} catch (error) {
const msg = extractErrorMessage(error);
const code = msg.includes('TIMEOUT') || msg.includes('timeout') ? 4 : 2;
return { ok: false, error: msg, code: code > 4 ? 2 : code };
}
} finally {
try {
await client.close();
} catch (_) {}
}
}
async function main() {
const url = process.argv[2];
if (!url) {
outJson(false, 'usage: node ping.js <tg:// or https://t.me/proxy?...>', 2);
process.exit(2);
}
const timeoutMs = parseInt(process.env.MTPROXY_TDLIB_PING_MS || '45000', 10) || 45000;
try {
const { server, port, secret } = parseProxyUrl(url);
const hexSecret = normalizeSecret(secret);
const r = await verifyProxy(server, port, hexSecret, timeoutMs);
outJson(r.ok, r.error, r.code);
process.exit(r.code);
} catch (e) {
if (e.message === 'INVALID_SECRET' || e.message.includes('INVALID_SECRET')) {
outJson(false, 'INVALID_SECRET', 1);
process.exit(1);
}
outJson(false, e.message || String(e), 2);
process.exit(2);
}
}
main().catch((e) => {
outJson(false, e.message || String(e), 2);
process.exit(2);
});