feat(telegram): add /belo command with current quote and daily progress

This commit is contained in:
Jose Selesan
2026-08-20 14:17:31 -03:00
parent 0407a79e1e
commit 96f6b26a76
2 changed files with 113 additions and 3 deletions

View File

@@ -1,6 +1,12 @@
import { Bot } from "grammy";
import {
formatPrice,
formatTime,
getBeloQuoteStatus,
isChatValidated,
validateCode,
} from "../modules/telegram/telegram.service";
import { logger } from "./logger";
import { validateCode } from "../modules/telegram/telegram.service";
const token = process.env.BOT_TOKEN;
@@ -30,6 +36,38 @@ export async function startBot(): Promise<void> {
);
});
bot.command("belo", async (ctx) => {
const validated = await isChatValidated(BigInt(ctx.chat.id));
if (!validated) {
await ctx.reply(
"🔒 Tu cuenta no está conectada. Enviá /start para ver cómo conectarla.",
);
return;
}
const status = await getBeloQuoteStatus();
if (!status) {
await ctx.reply("❌ No hay datos de BELO disponibles todavía.");
return;
}
const progress = Math.round(status.progressPercentage ?? 0);
const message = [
"💰 <b>BELO · USDC/ARS</b>",
"",
`Compra: <b>$${formatPrice(status.buy)}</b>`,
`Venta: <b>$${formatPrice(status.sell)}</b>`,
"",
`Mínimo del día: $${formatPrice(status.minBuy)}`,
`Máximo del día: $${formatPrice(status.maxBuy)}`,
`Avance del día: <b>${progress}%</b>`,
"",
`🕐 ${formatTime(status.timeStamp)}`,
].join("\n");
await ctx.reply(message, { parse_mode: "HTML" });
});
bot.on("message:text", async (ctx) => {
const text = ctx.message.text.trim();
if (text.startsWith("/")) return;

View File

@@ -13,7 +13,7 @@ function generateRandomCode(): string {
return code;
}
function formatPrice(price: number): string {
export function formatPrice(price: number): string {
return price.toLocaleString("es-AR", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
@@ -72,7 +72,7 @@ export async function getTelegramStatus(): Promise<{
};
}
function formatTime(date: Date): string {
export function formatTime(date: Date): string {
const artOffset = -3;
const artMs = date.getTime() + artOffset * 3600000;
const artDate = new Date(artMs);
@@ -208,3 +208,75 @@ export async function checkAndNotifyBelo(): Promise<void> {
todayStart,
});
}
export interface BeloQuoteStatus {
buy: number;
sell: number;
timeStamp: Date;
minBuy: number;
maxBuy: number;
minSell: number;
maxSell: number;
progressPercentage: number | null;
}
export async function getBeloQuoteStatus(): Promise<BeloQuoteStatus | null> {
const currentQuote = await prisma.quote.findFirst({
where: { type: "BELO" },
orderBy: { timeStamp: "desc" },
});
if (!currentQuote) return null;
const now = new Date();
const artOffset = -3;
const artMs = now.getTime() + artOffset * 3600000;
const artDate = new Date(artMs);
const todayStart = new Date(
Date.UTC(
artDate.getUTCFullYear(),
artDate.getUTCMonth(),
artDate.getUTCDate(),
),
);
const agg = await prisma.quoteHistory.aggregate({
where: {
type: "BELO",
timeStamp: { gte: todayStart },
},
_min: { buy: true, sell: true },
_max: { buy: true, sell: true },
});
const currentBuy = Number(currentQuote.buy);
const minBuy = Number(agg._min.buy ?? currentQuote.buy);
const maxBuy = Number(agg._max.buy ?? currentQuote.buy);
let progressPercentage: number | null = null;
if (maxBuy > minBuy) {
progressPercentage = Math.min(
100,
Math.max(0, ((currentBuy - minBuy) / (maxBuy - minBuy)) * 100),
);
} else {
progressPercentage = 100;
}
return {
buy: currentBuy,
sell: Number(currentQuote.sell),
timeStamp: currentQuote.timeStamp,
minBuy,
maxBuy,
minSell: Number(agg._min.sell ?? currentQuote.sell),
maxSell: Number(agg._max.sell ?? currentQuote.sell),
progressPercentage,
};
}
export async function isChatValidated(chatId: bigint): Promise<boolean> {
const chat = await prisma.telegramChat.findFirst({
where: { chatId, validatedAt: { not: null } },
});
return chat !== null;
}