Spaces:
Running
Running
| /** | |
| * GoldRate Engine โ Telegram Bot for API Key Management | |
| * | |
| * Private admin bot to create, list, revoke, and check API keys. | |
| * Only authorized admin Telegram IDs can use this bot. | |
| * | |
| * Commands: | |
| * /start โ Welcome message | |
| * /create_sln โ Create SLN (Chennai) API key | |
| * /create_ibja โ Create IBJA (India) API key | |
| * /list โ List all active keys | |
| * /revoke โ Revoke a key | |
| * /check โ Check key status | |
| * /help โ Show all commands | |
| * | |
| * Setup: | |
| * 1. Create a bot via @BotFather on Telegram | |
| * 2. Add TELEGRAM_BOT_TOKEN to .env | |
| * 3. Add your Telegram user ID to TELEGRAM_ADMIN_IDS in .env | |
| * 4. Start: node src/telegramBot.js (or auto-starts with server) | |
| */ | |
| require('dotenv').config(); | |
| const TelegramBotModule = require('node-telegram-bot-api'); | |
| const TelegramBot = TelegramBotModule.default || TelegramBotModule; | |
| const crypto = require('crypto'); | |
| const { createClient } = require('@supabase/supabase-js'); | |
| // โโ Config โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN; | |
| const ADMIN_IDS = (process.env.TELEGRAM_ADMIN_IDS || '') | |
| .split(',') | |
| .map(id => id.trim()) | |
| .filter(Boolean); | |
| let supabase = null; | |
| let bot = null; | |
| // โโ Initialize โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| function initTelegramBot() { | |
| if (!BOT_TOKEN) { | |
| console.log('โน๏ธ TELEGRAM_BOT_TOKEN not set โ Telegram bot disabled'); | |
| return null; | |
| } | |
| const supabaseUrl = process.env.SUPABASE_URL; | |
| const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY; | |
| if (!supabaseUrl || !supabaseKey) { | |
| console.log('โ ๏ธ Supabase not configured โ Telegram bot disabled'); | |
| return null; | |
| } | |
| supabase = createClient(supabaseUrl, supabaseKey); | |
| bot = new TelegramBot(BOT_TOKEN, { polling: true }); | |
| // Global error handler โ prevent crashes | |
| bot.on('polling_error', (err) => { | |
| console.warn('โ ๏ธ Telegram polling error:', err.message); | |
| }); | |
| bot.on('error', (err) => { | |
| console.warn('โ ๏ธ Telegram error:', err.message); | |
| }); | |
| // Register handlers | |
| bot.onText(/\/start/, safeHandler(handleStart)); | |
| bot.onText(/\/help/, safeHandler(handleHelp)); | |
| bot.onText(/\/create_sln(.*)/, safeHandler(handleCreateSLN)); | |
| bot.onText(/\/create_ibja(.*)/, safeHandler(handleCreateIBJA)); | |
| bot.onText(/\/list/, safeHandler(handleList)); | |
| bot.onText(/\/revoke(.*)/, safeHandler(handleRevoke)); | |
| bot.onText(/\/check(.*)/, safeHandler(handleCheck)); | |
| console.log('๐ค Telegram Bot started! Waiting for commands...'); | |
| if (ADMIN_IDS.length > 0) { | |
| console.log(`๐ Authorized admins: ${ADMIN_IDS.join(', ')}`); | |
| } else { | |
| console.log('โ ๏ธ No TELEGRAM_ADMIN_IDS set โ bot will tell you your ID on /start'); | |
| } | |
| return bot; | |
| } | |
| // โโ Safe handler wrapper (prevents crashes) โโโโโโโโโโ | |
| function safeHandler(fn) { | |
| return async (msg, match) => { | |
| try { | |
| await fn(msg, match); | |
| } catch (err) { | |
| console.error('โ ๏ธ Telegram handler error:', err.message); | |
| try { | |
| await bot.sendMessage(msg.chat.id, `โ Error: ${err.message}`); | |
| } catch (e) { | |
| console.error('โ ๏ธ Could not send error message:', e.message); | |
| } | |
| } | |
| }; | |
| } | |
| // โโ Auth Check โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| function isAdmin(chatId) { | |
| if (ADMIN_IDS.length === 0) return true; | |
| return ADMIN_IDS.includes(chatId.toString()); | |
| } | |
| function generateApiKey(source) { | |
| const prefix = source.toLowerCase(); | |
| const random = crypto.randomBytes(16).toString('hex'); | |
| return `${prefix}_${random}`; | |
| } | |
| // โโ HTML helper โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| function esc(text) { | |
| return String(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); | |
| } | |
| // โโ /start โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async function handleStart(msg) { | |
| const chatId = msg.chat.id; | |
| const name = esc(msg.from.first_name || 'Admin'); | |
| const adminStatus = isAdmin(chatId) | |
| ? 'โ You are authorized as admin' | |
| : 'โ Not authorized. Add your ID to TELEGRAM_ADMIN_IDS in .env'; | |
| const text = | |
| `๐ช <b>GoldRate Engine โ API Key Bot</b> | |
| Welcome ${name}! ๐ | |
| Your Telegram ID: <code>${chatId}</code> | |
| ${adminStatus} | |
| <b>Available Commands:</b> | |
| /create_sln <code>Owner Name</code> โ Chennai (SLN) key | |
| /create_ibja <code>Owner Name</code> โ India (IBJA) key | |
| /list โ List all API keys | |
| /check <code>api_key</code> โ Check key details | |
| /revoke <code>api_key</code> โ Deactivate a key | |
| /help โ Show this message`; | |
| await bot.sendMessage(chatId, text, { parse_mode: 'HTML' }); | |
| } | |
| // โโ /help โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async function handleHelp(msg) { | |
| return handleStart(msg); | |
| } | |
| // โโ /create_sln โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async function handleCreateSLN(msg, match) { | |
| const chatId = msg.chat.id; | |
| if (!isAdmin(chatId)) { | |
| return bot.sendMessage(chatId, 'โ Unauthorized. Contact admin.'); | |
| } | |
| const args = (match[1] || '').trim(); | |
| if (!args) { | |
| return bot.sendMessage(chatId, | |
| 'โ ๏ธ Usage: /create_sln <code>Owner Name [Days]</code>\n\nExample: /create_sln Gold Shop ABC 90', | |
| { parse_mode: 'HTML' }); | |
| } | |
| // Parse name and optional days (e.g. "Shop Name 90" -> "Shop Name" and 90 days) | |
| const parts = args.split(/\s+/); | |
| let days = 30; | |
| let ownerName = args; | |
| if (parts.length > 1) { | |
| const lastPart = parts[parts.length - 1]; | |
| if (/^\d+$/.test(lastPart)) { | |
| days = parseInt(lastPart); | |
| ownerName = parts.slice(0, -1).join(' '); | |
| } | |
| } | |
| await createKey(chatId, ownerName, 'SLN', days); | |
| } | |
| // โโ /create_ibja โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async function handleCreateIBJA(msg, match) { | |
| const chatId = msg.chat.id; | |
| if (!isAdmin(chatId)) { | |
| return bot.sendMessage(chatId, 'โ Unauthorized. Contact admin.'); | |
| } | |
| const args = (match[1] || '').trim(); | |
| if (!args) { | |
| return bot.sendMessage(chatId, | |
| 'โ ๏ธ Usage: /create_ibja <code>Owner Name [Days]</code>\n\nExample: /create_ibja Jeweller XYZ 120', | |
| { parse_mode: 'HTML' }); | |
| } | |
| // Parse name and optional days | |
| const parts = args.split(/\s+/); | |
| let days = 30; | |
| let ownerName = args; | |
| if (parts.length > 1) { | |
| const lastPart = parts[parts.length - 1]; | |
| if (/^\d+$/.test(lastPart)) { | |
| days = parseInt(lastPart); | |
| ownerName = parts.slice(0, -1).join(' '); | |
| } | |
| } | |
| await createKey(chatId, ownerName, 'IBJA', days); | |
| } | |
| // โโ Create Key (shared) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async function createKey(chatId, ownerName, source, days = 30) { | |
| const apiKey = generateApiKey(source); | |
| const expiresAt = new Date(); | |
| expiresAt.setDate(expiresAt.getDate() + days); | |
| const { data, error } = await supabase | |
| .from('api_keys') | |
| .insert({ | |
| api_key: apiKey, | |
| owner_name: ownerName, | |
| source: source, | |
| expires_at: expiresAt.toISOString(), | |
| is_active: true, | |
| notes: `Created via Telegram by ${chatId}` | |
| }) | |
| .select() | |
| .single(); | |
| if (error) { | |
| return bot.sendMessage(chatId, | |
| `โ Failed to create key: ${esc(error.message)}\n\n๐ก Did you create the <code>api_keys</code> table in Supabase?`, | |
| { parse_mode: 'HTML' }); | |
| } | |
| const expiryStr = expiresAt.toLocaleDateString('en-IN', { | |
| day: '2-digit', month: 'short', year: 'numeric' | |
| }); | |
| const sourceName = source === 'SLN' ? 'SLN Bullion Chennai' : 'IBJA India'; | |
| const text = | |
| `โ <b>API Key Created!</b> | |
| ๐ <b>Key:</b> | |
| <code>${apiKey}</code> | |
| ๐ค <b>Owner:</b> ${esc(ownerName)} | |
| ๐ <b>Source:</b> ${sourceName} | |
| ๐ <b>Valid Until:</b> ${expiryStr} | |
| โฑ <b>Duration:</b> ${days} days | |
| Copy the key above and share with the user.`; | |
| await bot.sendMessage(chatId, text, { parse_mode: 'HTML' }); | |
| } | |
| // โโ /list โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async function handleList(msg) { | |
| const chatId = msg.chat.id; | |
| if (!isAdmin(chatId)) { | |
| return bot.sendMessage(chatId, 'โ Unauthorized.'); | |
| } | |
| const { data, error } = await supabase | |
| .from('api_keys') | |
| .select('*') | |
| .order('created_at', { ascending: false }) | |
| .limit(20); | |
| if (error) { | |
| return bot.sendMessage(chatId, `โ Error: ${esc(error.message)}`); | |
| } | |
| if (!data || data.length === 0) { | |
| return bot.sendMessage(chatId, | |
| '๐ No API keys found.\n\nCreate one with /create_sln or /create_ibja'); | |
| } | |
| const now = new Date(); | |
| let text = `๐ <b>API Keys (${data.length})</b>\n\n`; | |
| for (const key of data) { | |
| const expired = new Date(key.expires_at) < now; | |
| const status = !key.is_active ? 'โ Revoked' : expired ? 'โฐ Expired' : 'โ Active'; | |
| const daysLeft = Math.max(0, Math.ceil((new Date(key.expires_at) - now) / (1000 * 60 * 60 * 24))); | |
| const expiryStr = new Date(key.expires_at).toLocaleDateString('en-IN', { day: '2-digit', month: 'short' }); | |
| text += `${status} <b>${esc(key.owner_name)}</b> (${key.source})\n`; | |
| text += `<code>${key.api_key.substring(0, 20)}...</code>\n`; | |
| text += `Expires: ${expiryStr}${!expired && key.is_active ? ` (${daysLeft}d left)` : ''}\n\n`; | |
| } | |
| text += 'Use /check <code>KEY</code> to see full details'; | |
| await bot.sendMessage(chatId, text, { parse_mode: 'HTML' }); | |
| } | |
| // โโ /revoke โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async function handleRevoke(msg, match) { | |
| const chatId = msg.chat.id; | |
| if (!isAdmin(chatId)) { | |
| return bot.sendMessage(chatId, 'โ Unauthorized.'); | |
| } | |
| const apiKey = (match[1] || '').trim(); | |
| if (!apiKey) { | |
| return bot.sendMessage(chatId, 'โ ๏ธ Usage: /revoke <code>api_key_here</code>', { parse_mode: 'HTML' }); | |
| } | |
| const { data, error } = await supabase | |
| .from('api_keys') | |
| .update({ is_active: false }) | |
| .eq('api_key', apiKey) | |
| .select() | |
| .single(); | |
| if (error || !data) { | |
| return bot.sendMessage(chatId, `โ Key not found: <code>${esc(apiKey)}</code>`, { parse_mode: 'HTML' }); | |
| } | |
| await bot.sendMessage(chatId, | |
| `โ <b>Key Revoked</b>\n\n๐ค Owner: ${esc(data.owner_name)}\n๐ Source: ${data.source}\n๐ Key: <code>${esc(apiKey)}</code>`, | |
| { parse_mode: 'HTML' }); | |
| } | |
| // โโ /check โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async function handleCheck(msg, match) { | |
| const chatId = msg.chat.id; | |
| if (!isAdmin(chatId)) { | |
| return bot.sendMessage(chatId, 'โ Unauthorized.'); | |
| } | |
| const apiKey = (match[1] || '').trim(); | |
| if (!apiKey) { | |
| return bot.sendMessage(chatId, 'โ ๏ธ Usage: /check <code>api_key_here</code>', { parse_mode: 'HTML' }); | |
| } | |
| const { data, error } = await supabase | |
| .from('api_keys') | |
| .select('*') | |
| .eq('api_key', apiKey) | |
| .single(); | |
| if (error || !data) { | |
| return bot.sendMessage(chatId, `โ Key not found: <code>${esc(apiKey)}</code>`, { parse_mode: 'HTML' }); | |
| } | |
| const now = new Date(); | |
| const expired = new Date(data.expires_at) < now; | |
| const daysLeft = Math.max(0, Math.ceil((new Date(data.expires_at) - now) / (1000 * 60 * 60 * 24))); | |
| const status = !data.is_active ? 'โ Revoked' : expired ? 'โฐ Expired' : 'โ Active'; | |
| const createdStr = new Date(data.created_at).toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); | |
| const expiryStr = new Date(data.expires_at).toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); | |
| const sourceName = data.source === 'SLN' ? '๐ SLN Bullion Chennai' : '๐ฎ๐ณ IBJA India'; | |
| const daysInfo = (!expired && data.is_active) ? `\n<b>Days Left:</b> ${daysLeft}` : ''; | |
| const text = | |
| `๐ <b>API Key Details</b> | |
| <b>Key:</b> <code>${data.api_key}</code> | |
| <b>Owner:</b> ${esc(data.owner_name)} | |
| <b>Phone:</b> ${data.owner_phone || 'N/A'} | |
| <b>Email:</b> ${data.owner_email || 'N/A'} | |
| <b>Source:</b> ${sourceName} | |
| <b>Status:</b> ${status} | |
| <b>Created:</b> ${createdStr} | |
| <b>Expires:</b> ${expiryStr}${daysInfo}`; | |
| await bot.sendMessage(chatId, text, { parse_mode: 'HTML' }); | |
| } | |
| // โโ Export โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| module.exports = { initTelegramBot }; | |
| // โโ Run standalone โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| if (require.main === module) { | |
| console.log('๐ค Starting Telegram Bot standalone...'); | |
| const botInstance = initTelegramBot(); | |
| if (!botInstance) { | |
| console.error('โ Bot could not start. Check TELEGRAM_BOT_TOKEN in .env'); | |
| process.exit(1); | |
| } | |
| } | |