File size: 3,516 Bytes
94ad3aa b52345c 94ad3aa b52345c 94ad3aa 6112db7 94ad3aa b52345c 94ad3aa 6112db7 b52345c 94ad3aa bce154c b52345c 6112db7 b52345c 6112db7 b52345c 6112db7 bce154c b52345c 6112db7 bce154c b52345c bce154c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | /**
* Cliente de integracion con Telegram Bot API.
*
* Responsabilidades:
* - sendMessage(chatId, text) → POST a api.telegram.org/bot<TOKEN>/sendMessage.
* - formatPriceAlert(question, yesPrice, threshold) → construye mensaje HTML
* escapando entidades del input de usuario.
* - escapeHtml(text) → escapa caracteres HTML sensibles (&, <, >, ").
* - Parse_mode: HTML (permite formato basico: <b>, <i>).
* - Si falta botToken o chatId, sendMessage retorna silenciosamente.
* - Errores de red se capturan y loguean como warn (no bloquean el flujo).
* - Respuestas de Telegram con ok: false se loguean como warn y no lanzan excepcion.
*
* Consumido por:
* - alerts.service.js → processAll() cuando se dispara una alerta.
*
* Seguridad:
* - El token se pasa por argumento desde el registro del usuario (nunca hardcodeado).
* - Todo texto dinamico se escapa via escapeHtml antes de inyectarse en HTML.
*/
import { httpPost } from '../utils/httpClient.js';
import { logger } from '../utils/logger.js';
/**
* Escapa caracteres especiales HTML para evitar inyeccion
* cuando se usa parse_mode='HTML' en la API de Telegram.
*
* @param {string} text - Texto a escapar.
* @returns {string} Texto con entidades HTML escapadas (&, <, >, ").
*/
export function escapeHtml(text) {
if (typeof text !== 'string') return String(text ?? '');
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
/**
* Formatea un mensaje de alerta de precio en HTML para Telegram.
*
* Escapa automaticamente la pregunta del mercado para evitar inyeccion HTML.
* Los tags <b> son intencionales y no se escapan.
*
* @param {string} question - Pregunta del mercado (se escapa automaticamente).
* @param {number} yesPrice - Precio YES actual (0-1).
* @param {number} threshold - Umbral que se cruzo (0-1).
* @returns {string} Mensaje formateado en HTML.
*/
export function formatPriceAlert(question, yesPrice, threshold) {
const safeQuestion = escapeHtml(question);
const pct = (yesPrice * 100).toFixed(1);
const thr = (threshold * 100).toFixed(1);
return `<b>Price Alert</b>\n${safeQuestion}\nYES: ${pct}% ≥ threshold ${thr}%`;
}
/**
* Envia un mensaje de texto a un chat de Telegram via Bot API.
*
* Si botToken no esta configurado o chatId es falsy, la funcion
* retorna silenciosamente sin realizar peticion.
*
* Ante errores de red o respuestas con ok: false de Telegram, se loguea un
* warning y la funcion retorna sin lanzar excepcion, evitando interrumpir
* el flujo del scheduler.
*
* @param {Object} params
* @param {string} params.botToken - Token del bot de Telegram.
* @param {string|number} params.chatId - Identificador del chat de Telegram.
* @param {string} params.text - Texto del mensaje (debe estar ya formateado en HTML).
* @returns {Promise<void>}
*/
export async function sendMessage({ botToken, chatId, text }) {
if (!botToken || !chatId) return;
try {
const result = await httpPost(
`https://api.telegram.org/bot${botToken}/sendMessage`,
{ chat_id: chatId, text, parse_mode: 'HTML' },
{ retries: 1, timeout: 8_000 },
);
if (result && result.ok === false) {
logger.warn(
{ chatId, description: result.description, errorCode: result.error_code },
'telegram API returned ok=false',
);
return;
}
} catch (err) {
logger.warn({ err: err.message, chatId }, 'telegram send failed');
}
}
|