154 lines
4.7 KiB
JavaScript
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);
|
|
});
|