Files
mtproxy_checker/contrib/tdlib-ping/ping.js
T

154 lines
4.7 KiB
JavaScript

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