integrate telegram bot
This commit is contained in:
@@ -1,31 +1,64 @@
|
||||
import https from 'node:https';
|
||||
import tls from 'node:tls';
|
||||
import { Buffer } from 'node:buffer';
|
||||
import { config } from './env.js';
|
||||
import { connectThroughProxy } from './proxy.js';
|
||||
|
||||
const API_BASE = `https://api.telegram.org/bot${config.botToken}`;
|
||||
const API_HOST = 'api.telegram.org';
|
||||
|
||||
async function callApi(method, payload, { timeoutMs = 10_000 } = {}) {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
const { status, text } = await postJson(`/bot${config.botToken}/${method}`, payload, timeoutMs);
|
||||
|
||||
let body = {};
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/${method}`, {
|
||||
body = JSON.parse(text);
|
||||
} catch {
|
||||
// keep the empty body; the status check below reports it
|
||||
}
|
||||
|
||||
if (status < 200 || status >= 300 || body.ok !== true) {
|
||||
// Telegram carries the real failure in `description` (e.g. "chat not found" when the
|
||||
// recipient never messaged the bot first) — surface it verbatim so the cause is obvious.
|
||||
throw new Error(body.description ?? `HTTP ${status}`);
|
||||
}
|
||||
|
||||
return body.result;
|
||||
}
|
||||
|
||||
/**
|
||||
* One HTTPS POST to Telegram. Built on `node:https` rather than `fetch` so the optional proxy applies
|
||||
* identically on every supported Node version: with a proxy configured the request rides a tunnelled socket
|
||||
* (see `proxy.js`), without one it takes the default direct route.
|
||||
*/
|
||||
async function postJson(path, payload, timeoutMs) {
|
||||
const body = JSON.stringify(payload);
|
||||
const tunnel = config.proxy
|
||||
? await connectThroughProxy(config.proxy, API_HOST, 443, timeoutMs)
|
||||
: null;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = https.request({
|
||||
host: API_HOST,
|
||||
port: 443,
|
||||
path,
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
signal: controller.signal,
|
||||
headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body) },
|
||||
// Only set when tunnelling: passing createConnection is what makes node skip the default agent.
|
||||
...(tunnel ? { createConnection: () => tls.connect({ socket: tunnel, servername: API_HOST }) } : {}),
|
||||
}, (response) => {
|
||||
let text = '';
|
||||
response.setEncoding('utf8');
|
||||
response.on('data', (chunk) => { text += chunk; });
|
||||
response.on('end', () => resolve({ status: response.statusCode, text }));
|
||||
});
|
||||
|
||||
const body = await response.json().catch(() => ({}));
|
||||
if (!response.ok || body.ok !== true) {
|
||||
// Telegram carries the real failure in `description` (e.g. "chat not found" when the
|
||||
// recipient never messaged the bot first) — surface it verbatim so the cause is obvious.
|
||||
const reason = body.description ?? `HTTP ${response.status}`;
|
||||
throw new Error(reason);
|
||||
}
|
||||
|
||||
return body.result;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
request.setTimeout(timeoutMs, () => request.destroy(new Error(`no response within ${timeoutMs}ms`)));
|
||||
request.on('error', (error) => {
|
||||
if (tunnel) tunnel.destroy();
|
||||
reject(error);
|
||||
});
|
||||
request.end(body);
|
||||
});
|
||||
}
|
||||
|
||||
/** Sends one message to every configured chat id. Never rejects — per-recipient outcomes are returned. */
|
||||
|
||||
Reference in New Issue
Block a user