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")
|
@@map("telegram_chats")
|
||||||
}
|
}
|
||||||
|
|
||||||
model TelegramDailyMaxNotification {
|
enum PriceDirection {
|
||||||
id String @id @default(cuid())
|
MAX
|
||||||
|
MIN
|
||||||
|
}
|
||||||
|
|
||||||
|
model TelegramDailyPriceNotification {
|
||||||
|
id String @id @default(cuid())
|
||||||
date DateTime
|
date DateTime
|
||||||
quoteType QuoteType
|
quoteType QuoteType
|
||||||
maxValue Decimal @db.Money
|
direction PriceDirection
|
||||||
createdAt DateTime @default(now())
|
value Decimal @db.Money
|
||||||
updatedAt DateTime @updatedAt
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@unique([date, quoteType])
|
@@unique([date, quoteType, direction])
|
||||||
@@map("telegram_daily_max_notifications")
|
@@map("telegram_daily_price_notifications")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum QuoteType {
|
enum QuoteType {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import cron from "node-cron";
|
import cron from "node-cron";
|
||||||
import { eventBus } from "../../lib/event-bus";
|
import { eventBus } from "../../lib/event-bus";
|
||||||
import { logger } from "../../lib/logger";
|
import { logger } from "../../lib/logger";
|
||||||
import { checkAndNotifyBeloMax } from "../telegram/telegram.service";
|
import { checkAndNotifyBelo } from "../telegram/telegram.service";
|
||||||
import { processBeloQuote } from "./belo.service";
|
import { processBeloQuote } from "./belo.service";
|
||||||
import { processBlueQuote } from "./blue.service";
|
import { processBlueQuote } from "./blue.service";
|
||||||
import { processBnaQuote } from "./bna.service";
|
import { processBnaQuote } from "./bna.service";
|
||||||
@@ -14,7 +14,7 @@ async function safeProcess(
|
|||||||
await fn();
|
await fn();
|
||||||
logger.info(`${name} quote processed successfully`);
|
logger.info(`${name} quote processed successfully`);
|
||||||
if (name === "BELO") {
|
if (name === "BELO") {
|
||||||
await checkAndNotifyBeloMax();
|
await checkAndNotifyBelo();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error, `Error processing ${name} quote`);
|
logger.error(error, `Error processing ${name} quote`);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { prisma } from "../../lib/prisma";
|
|
||||||
import { logger } from "../../lib/logger";
|
import { logger } from "../../lib/logger";
|
||||||
|
import { prisma } from "../../lib/prisma";
|
||||||
import { getBot, getBotUsername } from "../../lib/telegram";
|
import { getBot, getBotUsername } from "../../lib/telegram";
|
||||||
|
|
||||||
function generateRandomCode(): string {
|
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({
|
const chat = await prisma.telegramChat.findFirst({
|
||||||
where: { validatedAt: { not: null } },
|
where: { validatedAt: { not: null } },
|
||||||
orderBy: { validatedAt: "desc" },
|
orderBy: { validatedAt: "desc" },
|
||||||
@@ -84,7 +170,7 @@ export async function checkAndNotifyBeloMax(): Promise<void> {
|
|||||||
if (!bot) return;
|
if (!bot) return;
|
||||||
|
|
||||||
const now = new Date();
|
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;
|
if (artHour < 8 || artHour >= 20) return;
|
||||||
|
|
||||||
const currentQuote = await prisma.quote.findFirst({
|
const currentQuote = await prisma.quote.findFirst({
|
||||||
@@ -106,46 +192,19 @@ export async function checkAndNotifyBeloMax(): Promise<void> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const lastNotified = await prisma.telegramDailyMaxNotification.findUnique({
|
await checkAndNotifyBound({
|
||||||
where: {
|
chatId: chat.chatId,
|
||||||
date_quoteType: {
|
direction: "MAX",
|
||||||
date: todayStart,
|
currentValue: currentBuy,
|
||||||
quoteType: "BELO",
|
quoteTime: currentQuote.timeStamp,
|
||||||
},
|
todayStart,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const lastNotifiedValue = lastNotified ? Number(lastNotified.maxValue) : 0;
|
await checkAndNotifyBound({
|
||||||
|
chatId: chat.chatId,
|
||||||
if (currentBuy <= lastNotifiedValue) return;
|
direction: "MIN",
|
||||||
|
currentValue: currentBuy,
|
||||||
const formattedPrice = formatPrice(currentBuy);
|
quoteTime: currentQuote.timeStamp,
|
||||||
const message = `BELO alcanzó el máximo del día, la cotización actual es de $${formattedPrice}`;
|
todayStart,
|
||||||
|
});
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user