#!/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 ', 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); });