Updated Telegram notification
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PriceDirection" AS ENUM ('MAX', 'MIN');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "telegram_daily_price_notifications" (
|
||||
"id" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"quoteType" "QuoteType" NOT NULL,
|
||||
"direction" "PriceDirection" NOT NULL,
|
||||
"value" MONEY NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "telegram_daily_price_notifications_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Migrate existing max records (if any)
|
||||
INSERT INTO "telegram_daily_price_notifications" ("id", "date", "quoteType", "direction", "value", "createdAt", "updatedAt")
|
||||
SELECT "id", "date", "quoteType", 'MAX'::"PriceDirection", "maxValue", "createdAt", "updatedAt"
|
||||
FROM "telegram_daily_max_notifications";
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "telegram_daily_price_notifications_date_quoteType_direction_key" ON "telegram_daily_price_notifications"("date", "quoteType", "direction");
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "telegram_daily_max_notifications";
|
||||
@@ -79,16 +79,22 @@ model TelegramChat {
|
||||
@@map("telegram_chats")
|
||||
}
|
||||
|
||||
model TelegramDailyMaxNotification {
|
||||
id String @id @default(cuid())
|
||||
enum PriceDirection {
|
||||
MAX
|
||||
MIN
|
||||
}
|
||||
|
||||
model TelegramDailyPriceNotification {
|
||||
id String @id @default(cuid())
|
||||
date DateTime
|
||||
quoteType QuoteType
|
||||
maxValue Decimal @db.Money
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
direction PriceDirection
|
||||
value Decimal @db.Money
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([date, quoteType])
|
||||
@@map("telegram_daily_max_notifications")
|
||||
@@unique([date, quoteType, direction])
|
||||
@@map("telegram_daily_price_notifications")
|
||||
}
|
||||
|
||||
enum QuoteType {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import cron from "node-cron";
|
||||
import { eventBus } from "../../lib/event-bus";
|
||||
import { logger } from "../../lib/logger";
|
||||
import { checkAndNotifyBeloMax } from "../telegram/telegram.service";
|
||||
import { checkAndNotifyBelo } from "../telegram/telegram.service";
|
||||
import { processBeloQuote } from "./belo.service";
|
||||
import { processBlueQuote } from "./blue.service";
|
||||
import { processBnaQuote } from "./bna.service";
|
||||
@@ -14,7 +14,7 @@ async function safeProcess(
|
||||
await fn();
|
||||
logger.info(`${name} quote processed successfully`);
|
||||
if (name === "BELO") {
|
||||
await checkAndNotifyBeloMax();
|
||||
await checkAndNotifyBelo();
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error, `Error processing ${name} quote`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from "crypto";
|
||||
import { prisma } from "../../lib/prisma";
|
||||
import { logger } from "../../lib/logger";
|
||||
import { prisma } from "../../lib/prisma";
|
||||
import { getBot, getBotUsername } from "../../lib/telegram";
|
||||
|
||||
function generateRandomCode(): string {
|
||||
@@ -72,7 +72,93 @@ export async function getTelegramStatus(): Promise<{
|
||||
};
|
||||
}
|
||||
|
||||
export async function checkAndNotifyBeloMax(): Promise<void> {
|
||||
function formatTime(date: Date): string {
|
||||
const artOffset = -3;
|
||||
const artMs = date.getTime() + artOffset * 3600000;
|
||||
const artDate = new Date(artMs);
|
||||
return artDate.toLocaleTimeString("es-AR", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
timeZone: "UTC",
|
||||
});
|
||||
}
|
||||
|
||||
async function checkAndNotifyBound({
|
||||
chatId,
|
||||
direction,
|
||||
currentValue,
|
||||
quoteTime,
|
||||
todayStart,
|
||||
}: {
|
||||
chatId: string | bigint;
|
||||
direction: "MAX" | "MIN";
|
||||
currentValue: number;
|
||||
quoteTime: Date;
|
||||
todayStart: Date;
|
||||
}): Promise<void> {
|
||||
const bot = getBot();
|
||||
if (!bot) return;
|
||||
|
||||
const isMax = direction === "MAX";
|
||||
|
||||
const lastNotified = await prisma.telegramDailyPriceNotification.findUnique({
|
||||
where: {
|
||||
date_quoteType_direction: {
|
||||
date: todayStart,
|
||||
quoteType: "BELO",
|
||||
direction,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const lastNotifiedValue = lastNotified
|
||||
? Number(lastNotified.value)
|
||||
: isMax
|
||||
? 0
|
||||
: Number.POSITIVE_INFINITY;
|
||||
|
||||
const shouldNotify = isMax
|
||||
? currentValue > lastNotifiedValue
|
||||
: currentValue < lastNotifiedValue;
|
||||
|
||||
if (!shouldNotify) return;
|
||||
|
||||
const formattedPrice = formatPrice(currentValue);
|
||||
const time = formatTime(quoteTime);
|
||||
const icon = isMax ? "🟢📈" : "🔴📉";
|
||||
const word = isMax ? "máximo" : "mínimo";
|
||||
const message = `${icon} BELO alcanzó el ${word} del día a las ${time}, cotización actual es de $${formattedPrice}`;
|
||||
|
||||
try {
|
||||
await bot.api.sendMessage(Number(chatId), message);
|
||||
|
||||
await prisma.telegramDailyPriceNotification.upsert({
|
||||
where: {
|
||||
date_quoteType_direction: {
|
||||
date: todayStart,
|
||||
quoteType: "BELO",
|
||||
direction,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
date: todayStart,
|
||||
quoteType: "BELO",
|
||||
direction,
|
||||
value: currentValue,
|
||||
},
|
||||
update: {
|
||||
value: currentValue,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info(`Telegram BELO ${word} notification sent: $${formattedPrice}`);
|
||||
} catch (error) {
|
||||
logger.error(error, `Failed to send Telegram BELO ${word} notification`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkAndNotifyBelo(): Promise<void> {
|
||||
const chat = await prisma.telegramChat.findFirst({
|
||||
where: { validatedAt: { not: null } },
|
||||
orderBy: { validatedAt: "desc" },
|
||||
@@ -84,7 +170,7 @@ export async function checkAndNotifyBeloMax(): Promise<void> {
|
||||
if (!bot) return;
|
||||
|
||||
const now = new Date();
|
||||
const artHour = ((now.getUTCHours() - 3) % 24 + 24) % 24;
|
||||
const artHour = (((now.getUTCHours() - 3) % 24) + 24) % 24;
|
||||
if (artHour < 8 || artHour >= 20) return;
|
||||
|
||||
const currentQuote = await prisma.quote.findFirst({
|
||||
@@ -106,46 +192,19 @@ export async function checkAndNotifyBeloMax(): Promise<void> {
|
||||
),
|
||||
);
|
||||
|
||||
const lastNotified = await prisma.telegramDailyMaxNotification.findUnique({
|
||||
where: {
|
||||
date_quoteType: {
|
||||
date: todayStart,
|
||||
quoteType: "BELO",
|
||||
},
|
||||
},
|
||||
await checkAndNotifyBound({
|
||||
chatId: chat.chatId,
|
||||
direction: "MAX",
|
||||
currentValue: currentBuy,
|
||||
quoteTime: currentQuote.timeStamp,
|
||||
todayStart,
|
||||
});
|
||||
|
||||
const lastNotifiedValue = lastNotified ? Number(lastNotified.maxValue) : 0;
|
||||
|
||||
if (currentBuy <= lastNotifiedValue) return;
|
||||
|
||||
const formattedPrice = formatPrice(currentBuy);
|
||||
const message = `BELO alcanzó el máximo del día, la cotización actual es de $${formattedPrice}`;
|
||||
|
||||
try {
|
||||
await bot.api.sendMessage(Number(chat.chatId), message);
|
||||
|
||||
await prisma.telegramDailyMaxNotification.upsert({
|
||||
where: {
|
||||
date_quoteType: {
|
||||
date: todayStart,
|
||||
quoteType: "BELO",
|
||||
},
|
||||
},
|
||||
create: {
|
||||
date: todayStart,
|
||||
quoteType: "BELO",
|
||||
maxValue: currentBuy,
|
||||
},
|
||||
update: {
|
||||
maxValue: currentBuy,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`Telegram BELO max notification sent: $${formattedPrice}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to send Telegram BELO max notification");
|
||||
}
|
||||
await checkAndNotifyBound({
|
||||
chatId: chat.chatId,
|
||||
direction: "MIN",
|
||||
currentValue: currentBuy,
|
||||
quoteTime: currentQuote.timeStamp,
|
||||
todayStart,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user