feat(telegram): add /belo command with current quote and daily progress
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
import { Bot } from "grammy";
|
import { Bot } from "grammy";
|
||||||
|
import {
|
||||||
|
formatPrice,
|
||||||
|
formatTime,
|
||||||
|
getBeloQuoteStatus,
|
||||||
|
isChatValidated,
|
||||||
|
validateCode,
|
||||||
|
} from "../modules/telegram/telegram.service";
|
||||||
import { logger } from "./logger";
|
import { logger } from "./logger";
|
||||||
import { validateCode } from "../modules/telegram/telegram.service";
|
|
||||||
|
|
||||||
const token = process.env.BOT_TOKEN;
|
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) => {
|
bot.on("message:text", async (ctx) => {
|
||||||
const text = ctx.message.text.trim();
|
const text = ctx.message.text.trim();
|
||||||
if (text.startsWith("/")) return;
|
if (text.startsWith("/")) return;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function generateRandomCode(): string {
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatPrice(price: number): string {
|
export function formatPrice(price: number): string {
|
||||||
return price.toLocaleString("es-AR", {
|
return price.toLocaleString("es-AR", {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
maximumFractionDigits: 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 artOffset = -3;
|
||||||
const artMs = date.getTime() + artOffset * 3600000;
|
const artMs = date.getTime() + artOffset * 3600000;
|
||||||
const artDate = new Date(artMs);
|
const artDate = new Date(artMs);
|
||||||
@@ -208,3 +208,75 @@ export async function checkAndNotifyBelo(): Promise<void> {
|
|||||||
todayStart,
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user