File size: 13,448 Bytes
dcc4f27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/**
 * 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

// โ”€โ”€ /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);
  }
}