xic-presale/server/telegram.ts

170 lines
5.0 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Telegram Notification Service
*
* Sends alerts to admin via Telegram Bot when:
* - New TRC20 purchase is confirmed
* - Purchase is marked as distributed
*
* Configuration (via environment variables or admin settings table):
* TELEGRAM_BOT_TOKEN — Bot token from @BotFather (e.g. 123456:ABC-DEF...)
* TELEGRAM_CHAT_ID — Chat ID to send messages to (personal or group)
*
* How to set up:
* 1. Open Telegram, search @BotFather, send /newbot
* 2. Follow prompts to get your Bot Token
* 3. Start a chat with your bot (or add it to a group)
* 4. Get Chat ID: https://api.telegram.org/bot<TOKEN>/getUpdates
* 5. Set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID in environment variables
*/
const TELEGRAM_API = "https://api.telegram.org";
export interface TelegramConfig {
botToken: string;
chatId: string;
}
/**
* Get Telegram config from environment variables or DB settings.
* Returns null if not configured.
*/
export async function getTelegramConfig(): Promise<TelegramConfig | null> {
const botToken = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
if (!botToken || !chatId) {
return null;
}
return { botToken, chatId };
}
/**
* Send a message via Telegram Bot API.
* Uses HTML parse mode for formatting.
*/
export async function sendTelegramMessage(
config: TelegramConfig,
message: string
): Promise<boolean> {
try {
const url = `${TELEGRAM_API}/bot${config.botToken}/sendMessage`;
const resp = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: config.chatId,
text: message,
parse_mode: "HTML",
disable_web_page_preview: true,
}),
signal: AbortSignal.timeout(10_000),
});
if (!resp.ok) {
const err = await resp.text();
console.warn(`[Telegram] Send failed (${resp.status}): ${err}`);
return false;
}
console.log("[Telegram] Message sent successfully");
return true;
} catch (e) {
console.warn("[Telegram] Send error:", e);
return false;
}
}
/**
* Notify admin about a new TRC20 purchase.
*/
export async function notifyNewTRC20Purchase(purchase: {
txHash: string;
fromAddress: string;
usdtAmount: number;
xicAmount: number;
evmAddress: string | null;
}): Promise<void> {
const config = await getTelegramConfig();
if (!config) {
console.log("[Telegram] Not configured — skipping notification (set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID)");
return;
}
const evmLine = purchase.evmAddress
? `\n🔑 <b>EVM Address:</b> <code>${purchase.evmAddress}</code>`
: "\n⚠ <b>EVM Address:</b> Not provided (manual distribution required)";
const txUrl = `https://tronscan.org/#/transaction/${purchase.txHash}`;
const message = [
"🟢 <b>New XIC Presale Purchase!</b>",
"",
`💰 <b>Amount:</b> $${purchase.usdtAmount.toFixed(2)} USDT`,
`🪙 <b>XIC Tokens:</b> ${purchase.xicAmount.toLocaleString()} XIC`,
`📍 <b>From (TRON):</b> <code>${purchase.fromAddress}</code>`,
evmLine,
`🔗 <b>TX:</b> <a href="${txUrl}">${purchase.txHash.slice(0, 16)}...</a>`,
"",
`${new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })} (CST)`,
].join("\n");
await sendTelegramMessage(config, message);
}
/**
* Notify admin when a purchase is marked as distributed.
*/
export async function notifyDistributed(purchase: {
txHash: string;
evmAddress: string;
xicAmount: number;
distributeTxHash?: string | null;
}): Promise<void> {
const config = await getTelegramConfig();
if (!config) return;
const distTxLine = purchase.distributeTxHash
? `\n🔗 <b>Dist TX:</b> <code>${purchase.distributeTxHash}</code>`
: "";
const message = [
"✅ <b>XIC Tokens Distributed!</b>",
"",
`🪙 <b>XIC Tokens:</b> ${purchase.xicAmount.toLocaleString()} XIC`,
`📬 <b>To EVM:</b> <code>${purchase.evmAddress}</code>`,
distTxLine,
`🔗 <b>Original TX:</b> <code>${purchase.txHash.slice(0, 16)}...</code>`,
"",
`${new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })} (CST)`,
].join("\n");
await sendTelegramMessage(config, message);
}
/**
* Test the Telegram connection with a test message.
* Returns { success, error } for admin UI feedback.
*/
export async function testTelegramConnection(
botToken: string,
chatId: string
): Promise<{ success: boolean; error?: string }> {
try {
const config: TelegramConfig = { botToken, chatId };
const message = [
"🔔 <b>NAC Presale — Telegram Notification Test</b>",
"",
"✅ Connection successful! You will receive purchase alerts here.",
"",
`${new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })} (CST)`,
].join("\n");
const ok = await sendTelegramMessage(config, message);
if (!ok) return { success: false, error: "Failed to send message. Check Bot Token and Chat ID." };
return { success: true };
} catch (e) {
return { success: false, error: e instanceof Error ? e.message : String(e) };
}
}