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.
This commit is contained in:
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user