Donyking1818 commited on
Commit
cb00eaf
·
verified ·
1 Parent(s): a9c1754

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +698 -333
index.js CHANGED
@@ -2,520 +2,887 @@ const {
2
  default: makeWASocket,
3
  DisconnectReason,
4
  useMultiFileAuthState,
5
- fetchLatestBaileysVersion
 
6
  } = require('@whiskeysockets/baileys');
7
  const QRCode = require('qrcode-terminal');
8
  const qrcode = require('qrcode');
9
  const axios = require('axios');
10
  const fs = require('fs');
11
  const path = require('path');
 
12
  const http = require('http');
 
 
 
 
 
13
 
14
  // ===== CONFIG =====
15
  const PORT = process.env.PORT || 7860;
16
  const HF_TOKEN = process.env.HF_TOKEN || '';
17
- const AUTH_FOLDER = path.join(__dirname, 'auth_info');
 
18
 
19
- // State management
20
  let sock = null;
21
  let qrCodeData = null;
 
22
  let connectionStatus = 'disconnected';
23
- let lastQRTime = null;
 
24
 
25
- // ===== HTTP SERVER (Web Interface + API) =====
26
- const server = http.createServer(async (req, res) => {
27
- res.setHeader('Access-Control-Allow-Origin', '*');
28
- res.setHeader('Content-Type', 'application/json');
29
-
30
- const url = req.url;
31
-
32
- // Health check
33
- if (url === '/') {
34
- res.end(JSON.stringify({
35
- status: 'running',
36
- bot: 'WhatsApp Bot',
37
- connection: connectionStatus,
38
- timestamp: new Date().toISOString()
39
- }));
40
- }
41
-
42
- // Get QR Code (Base64 image)
43
- else if (url === '/qr') {
44
- if (qrCodeData && connectionStatus === 'qr_ready') {
45
- res.setHeader('Content-Type', 'image/png');
46
- const qrBuffer = await qrcode.toBuffer(qrCodeData, {
47
- type: 'png',
48
- width: 400,
49
- margin: 2
50
- });
51
- res.end(qrBuffer);
52
- } else {
53
- res.end(JSON.stringify({
54
- status: connectionStatus,
55
- message: connectionStatus === 'connected'
56
- ? 'Sudah terhubung! Tidak perlu QR.'
57
- : 'QR belum siap, tunggu sebentar...',
58
- timestamp: new Date().toISOString()
59
- }));
60
- }
61
- }
62
-
63
- // Get Status
64
- else if (url === '/status') {
65
- res.end(JSON.stringify({
66
- status: connectionStatus,
67
- qr_available: !!qrCodeData,
68
- qr_age: lastQRTime ? Math.floor((Date.now() - lastQRTime) / 1000) : null,
69
- session_exists: fs.existsSync(AUTH_FOLDER),
70
- timestamp: new Date().toISOString()
71
- }));
72
- }
73
-
74
- // Force reconnect
75
- else if (url === '/reconnect') {
76
- if (sock) {
77
- sock.logout();
78
- res.end(JSON.stringify({ message: 'Reconnecting...' }));
79
- } else {
80
- res.end(JSON.stringify({ message: 'Bot not initialized' }));
81
- }
82
- }
83
-
84
- // HTML Interface
85
- else if (url === '/panel' || url === '/web') {
86
- res.setHeader('Content-Type', 'text/html');
87
- res.end(getWebInterface());
88
- }
89
-
90
- else {
91
- res.writeHead(404);
92
- res.end(JSON.stringify({ error: 'Not found' }));
93
- }
94
- });
95
-
96
- server.listen(PORT, () => {
97
- console.log(`🌐 Web server running on port ${PORT}`);
98
- console.log(`📱 Panel: http://localhost:${PORT}/panel`);
99
- console.log(`📊 Status: http://localhost:${PORT}/status`);
100
- console.log(`📷 QR Code: http://localhost:${PORT}/qr`);
101
- });
102
 
103
- // ===== WEB INTERFACE HTML =====
104
- function getWebInterface() {
105
- return `
106
  <!DOCTYPE html>
107
  <html lang="id">
108
  <head>
109
  <meta charset="UTF-8">
110
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
111
- <title>WhatsApp Bot Control Panel</title>
112
  <style>
113
  * { margin: 0; padding: 0; box-sizing: border-box; }
 
114
  body {
115
- font-family: 'Segoe UI', sans-serif;
116
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
117
  min-height: 100vh;
118
- padding: 20px;
119
- color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  }
 
121
  .container {
122
- max-width: 600px;
123
  margin: 0 auto;
 
 
 
124
  }
125
- h1 { text-align: center; margin-bottom: 30px; font-size: 2em; }
126
- .card {
127
- background: rgba(255,255,255,0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  backdrop-filter: blur(10px);
129
- border-radius: 20px;
130
- padding: 25px;
131
- margin-bottom: 20px;
132
- border: 1px solid rgba(255,255,255,0.2);
 
133
  }
134
- .status-box {
 
135
  display: flex;
136
  align-items: center;
137
- gap: 15px;
138
- margin-bottom: 20px;
139
  }
 
140
  .status-icon {
141
- width: 60px; height: 60px;
142
  border-radius: 50%;
143
  display: flex; align-items: center; justify-content: center;
144
- font-size: 28px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  }
146
- .connected { background: #4CAF50; }
147
- .disconnected { background: #f44336; }
148
- .qr_ready { background: #FF9800; animation: pulse 2s infinite; }
 
 
149
  @keyframes pulse {
150
  0%, 100% { transform: scale(1); }
151
  50% { transform: scale(1.05); }
152
  }
153
- .qr-container {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  padding: 20px;
 
 
 
156
  }
157
- .qr-container img {
158
- max-width: 300px;
159
- border-radius: 15px;
160
- background: white;
161
- padding: 15px;
 
 
 
 
 
 
162
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  .btn {
164
- width: 100%;
165
- padding: 15px;
166
  border: none;
167
- border-radius: 12px;
 
168
  font-size: 16px;
169
  font-weight: bold;
170
  cursor: pointer;
171
- margin-top: 10px;
172
  transition: all 0.3s;
 
 
 
173
  }
174
- .btn-primary { background: #4CAF50; color: white; }
175
- .btn-warning { background: #FF9800; color: white; }
176
- .btn:hover { transform: translateY(-2px); box-shadow: 0 5px 20px rgba(0,0,0,0.3); }
177
- .info-text {
178
- background: rgba(0,0,0,0.2);
179
- padding: 15px;
180
- border-radius: 10px;
181
- margin-top: 15px;
182
- font-size: 14px;
183
- line-height: 1.6;
184
  }
185
- .mode-indicator {
186
- display: inline-block;
187
- padding: 5px 15px;
188
- border-radius: 20px;
189
- font-size: 12px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  font-weight: bold;
191
- margin-top: 10px;
 
 
 
 
 
 
192
  }
193
- .mode-qr { background: #FF9800; }
194
- .mode-session { background: #2196F3; }
195
- .logs {
196
- background: rgba(0,0,0,0.3);
197
  padding: 15px;
198
- border-radius: 10px;
199
- font-family: monospace;
200
- font-size: 12px;
201
  max-height: 200px;
202
  overflow-y: auto;
203
- margin-top: 15px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  }
205
  </style>
206
  </head>
207
  <body>
 
 
208
  <div class="container">
209
- <h1>🤖 WhatsApp Bot Panel</h1>
210
-
211
- <div class="card">
212
- <div class="status-box">
213
- <div class="status-icon" id="statusIcon">⏳</div>
214
- <div>
215
- <h2 id="statusTitle">Memuat...</h2>
216
- <p id="statusDesc">Sedang cek koneksi...</p>
217
- <span class="mode-indicator" id="modeBadge">-</span>
 
 
 
218
  </div>
219
  </div>
220
 
221
- <div class="qr-container" id="qrContainer" style="display:none;">
222
- <h3>📱 Scan QR Code Ini</h3>
223
- <p style="margin-bottom:15px;opacity:0.9;">Buka WhatsApp → Menu → Linked Devices → Link a Device</p>
224
- <img id="qrImage" src="" alt="QR Code">
225
- <p style="margin-top:10px;font-size:12px;opacity:0.7;">QR Refresh otomatis setiap 20 detik</p>
 
 
 
 
 
 
 
 
226
  </div>
227
 
228
- <div id="connectedPanel" style="display:none;">
229
- <div style="text-align:center;padding:20px;">
230
- <div style="font-size:60px;margin-bottom:10px;">✅</div>
231
- <h3>Bot Terhubung!</h3>
232
- <p>Siap menerima pesan WhatsApp</p>
 
 
233
  </div>
234
- <button class="btn btn-warning" onclick="reconnect()">🔄 Reset & Scan Ulang</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  </div>
236
 
237
- <div class="logs" id="logs"></div>
238
- </div>
239
-
240
- <div class="card">
241
- <h3>📖 Cara Pakai:</h3>
242
- <div class="info-text">
243
- <strong>Mode 1 - QR (Pertama Kali):</strong><br>
244
- Scan QR code di atas pakai WhatsApp lu<br><br>
 
 
 
 
 
 
245
 
246
- <strong>Mode 2 - Session (Otomatis):</strong><br>
247
- Setelah scan pertama, bot auto-connect tanpa QR lagi<br><br>
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
- <strong>Command Bot:</strong><br>
250
- • Kirim <code>!ai [pertanyaan]</code> → Tanya AI<br>
251
- • Kirim <code>!help</code> → Bantuan<br>
252
- Kirim foto → AI analyze gambar
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  </div>
254
  </div>
 
 
 
 
 
255
  </div>
256
 
257
  <script>
258
- let refreshInterval;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
  async function checkStatus() {
261
  try {
262
- const res = await fetch('/status');
263
  const data = await res.json();
264
-
265
  updateUI(data);
266
  } catch (e) {
267
- addLog('Error: ' + e.message);
268
  }
269
  }
270
 
271
  function updateUI(data) {
 
272
  const icon = document.getElementById('statusIcon');
273
  const title = document.getElementById('statusTitle');
274
- const desc = document.getElementById('statusDesc');
275
- const modeBadge = document.getElementById('modeBadge');
276
- const qrContainer = document.getElementById('qrContainer');
277
- const connectedPanel = document.getElementById('connectedPanel');
278
 
279
  // Update status display
280
- if (data.status === 'connected') {
281
  icon.className = 'status-icon connected';
282
  icon.textContent = '✓';
283
  title.textContent = 'Terhubung';
284
- desc.textContent = 'Bot aktif & siap pakai';
285
- modeBadge.className = 'mode-indicator mode-session';
286
- modeBadge.textContent = 'MODE: SESSION (AUTO)';
287
- qrContainer.style.display = 'none';
288
- connectedPanel.style.display = 'block';
289
- addLog('✅ Bot terhubung ke WhatsApp');
290
- }
291
- else if (data.status === 'qr_ready') {
292
- icon.className = 'status-icon qr_ready';
 
 
 
293
  icon.textContent = '📱';
294
- title.textContent = 'Menunggu Scan QR';
295
- desc.textContent = 'Scan QR code untuk pairing';
296
- modeBadge.className = 'mode-indicator mode-qr';
297
- modeBadge.textContent = 'MODE: QR PAIRING';
298
- qrContainer.style.display = 'block';
299
- connectedPanel.style.display = 'none';
300
- document.getElementById('qrImage').src = '/qr?' + Date.now();
301
- addLog('📱 QR Code tersedia, silakan scan');
302
- }
303
- else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  icon.className = 'status-icon disconnected';
305
- icon.textContent = '';
306
- title.textContent = 'Terputus';
307
- desc.textContent = 'Menghubungkan ulang...';
308
- modeBadge.className = 'mode-indicator';
309
- modeBadge.textContent = 'MODE: -';
310
- qrContainer.style.display = 'none';
311
- connectedPanel.style.display = 'none';
312
  }
313
  }
314
 
315
- function addLog(msg) {
316
- const logs = document.getElementById('logs');
317
- const time = new Date().toLocaleTimeString();
318
- logs.innerHTML += \`[\\${time}] \\${msg}<br>\`;
319
- logs.scrollTop = logs.scrollHeight;
 
 
 
 
 
 
320
  }
321
 
322
- async function reconnect() {
323
- if (!confirm('Yakin mau reset? Perlu scan QR lagi.')) return;
324
- addLog('🔄 Mereset koneksi...');
325
- await fetch('/reconnect');
 
 
 
 
 
 
326
  setTimeout(checkStatus, 2000);
327
  }
328
 
329
- // Auto refresh
 
 
 
 
 
 
 
 
 
330
  checkStatus();
331
- refreshInterval = setInterval(checkStatus, 3000);
332
- addLog('🚀 Panel dimuat');
333
  </script>
334
  </body>
335
  </html>
336
- `;
337
- }
338
 
339
- // ===== WHATSAPP BOT CORE =====
340
- async function startBot() {
341
- console.log('🚀 Memulai WhatsApp Bot...');
342
-
343
- // Cek apakah ada session tersimpan
344
- const hasSession = fs.existsSync(AUTH_FOLDER) &&
345
- fs.readdirSync(AUTH_FOLDER).length > 0;
346
-
347
- if (hasSession) {
348
- console.log('📂 Session ditemukan! Mode: AUTO-CONNECT');
349
- console.log('🔄 Menghubungkan ke WhatsApp...');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  } else {
351
- console.log('📱 Session tidak ditemukan! Mode: QR PAIRING');
352
- console.log('⚠️ Tunggu QR code muncul...');
353
  }
 
 
 
 
 
 
 
 
354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  const { state, saveCreds } = await useMultiFileAuthState(AUTH_FOLDER);
356
  const { version } = await fetchLatestBaileysVersion();
357
 
 
 
 
358
  sock = makeWASocket({
359
  version,
 
360
  printQRInTerminal: true,
361
  auth: state,
362
  browser: ['Ubuntu', 'Chrome', '20.0.04'],
363
  generateHighQualityLinkPreview: true,
364
  syncFullHistory: false,
365
  markOnlineOnConnect: true,
366
- keepAliveIntervalMs: 30000, // Keep alive setiap 30 detik
367
  connectTimeoutMs: 60000,
368
  defaultQueryTimeoutMs: 60000,
 
 
369
  });
370
 
 
 
 
 
 
 
 
 
 
 
 
371
  // Event handlers
372
  sock.ev.on('connection.update', async (update) => {
373
  const { connection, lastDisconnect, qr } = update;
374
 
375
- // QR Code muncul
376
  if (qr) {
377
- console.log('\n🔥 QR CODE BARU:');
378
- QRCode.generate(qr, { small: true });
379
-
380
  qrCodeData = qr;
381
- lastQRTime = Date.now();
382
  connectionStatus = 'qr_ready';
383
-
384
- console.log('📷 QR juga tersedia di: /qr');
385
- console.log('🌐 Panel kontrol: /panel');
386
  }
387
 
388
- // Koneksi berubah
389
  if (connection === 'open') {
390
- console.log('\n✅ BERHASIL! Bot terhubung ke WhatsApp');
391
- console.log('📱 Nomor:', sock.user.id.split(':')[0]);
392
- console.log('🤖 Siap menerima pesan!\n');
393
-
394
  connectionStatus = 'connected';
395
  qrCodeData = null;
 
 
 
 
 
 
 
 
 
 
 
396
 
397
- // Kirim notifikasi ke diri sendiri (opsional)
398
  try {
399
  await sock.sendMessage(sock.user.id, {
400
- text: '🤖 *Bot Aktif!*\n\nBot sudah terhubung dan siap digunakan.\n\nKetik *!help* untuk bantuan.'
401
  });
402
- } catch (e) {
403
- // Ignore error
404
- }
405
  }
406
 
407
- // Koneksi tertutup
408
  else if (connection === 'close') {
409
  const statusCode = lastDisconnect?.error?.output?.statusCode;
410
  const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
411
 
412
- console.log('\n❌ Koneksi terputus:', statusCode);
413
  connectionStatus = 'disconnected';
414
- qrCodeData = null;
 
415
 
416
  if (shouldReconnect) {
417
- console.log('🔄 Reconnect otomatis dalam 5 detik...');
418
  setTimeout(startBot, 5000);
419
- } else {
420
- console.log('🚪 Logged out. Hapus folder auth_info untuk scan ulang.');
421
  }
422
  }
423
  });
424
 
425
- // Save credentials
426
  sock.ev.on('creds.update', saveCreds);
427
 
428
- // Handle messages
 
 
429
  sock.ev.on('messages.upsert', async ({ messages, type }) => {
430
  if (type !== 'notify') return;
431
 
432
  const msg = messages[0];
433
  if (!msg.message || msg.key.fromMe) return;
434
 
 
435
  const sender = msg.key.remoteJid;
436
- const isGroup = sender.endsWith('@g.us');
437
- const senderName = msg.pushName || 'User';
438
 
439
- // Extract text
440
- let text = '';
441
- if (msg.message.conversation) {
442
- text = msg.message.conversation;
443
- } else if (msg.message.extendedTextMessage?.text) {
444
- text = msg.message.extendedTextMessage.text;
445
- } else if (msg.message.imageMessage?.caption) {
446
- text = msg.message.imageMessage.caption;
447
- }
448
 
449
- console.log(`\n📩 Pesan dari ${senderName} (${isGroup ? 'Group' : 'Private'}):`);
450
- console.log(` "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
451
 
452
- // Command handler
453
  if (text.startsWith('!')) {
454
- await handleCommand(sock, msg, sender, text, senderName);
 
455
  }
456
- // Auto AI response (optional - reply semua pesan)
457
- else if (!isGroup) {
458
  await handleAIResponse(sock, sender, text);
 
459
  }
 
 
 
 
 
 
 
 
 
460
  });
461
  }
462
 
463
- // ===== COMMAND HANDLER =====
464
- async function handleCommand(sock, msg, sender, text, senderName) {
465
- const args = text.slice(1).trim().split(' ');
466
- const cmd = args.shift().toLowerCase();
467
 
468
  switch(cmd) {
469
  case 'help':
470
  await sock.sendMessage(sender, {
471
- text: `🤖 *Bot Commands:*\n\n` +
472
- `*!ai [pertanyaan]* Tanya AI\n` +
473
- `*!status* Cek status bot\n` +
474
- `*!ping* Test response\n\n` +
475
- `📷 Kirim foto untuk analyze gambar`
 
476
  });
477
  break;
478
-
479
- case 'ping':
480
- await sock.sendMessage(sender, { text: '🏓 Pong! Bot aktif.' });
 
 
 
 
481
  break;
482
-
483
  case 'status':
484
- const uptime = process.uptime();
485
- const hours = Math.floor(uptime / 3600);
486
- const mins = Math.floor((uptime % 3600) / 60);
487
  await sock.sendMessage(sender, {
488
- text: `📊 *Status Bot*\n\n` +
489
- `Status: ${connectionStatus}\n` +
490
- `Uptime: ${hours}j ${mins}m\n` +
491
- `Platform: Hugging Face Space`
492
  });
493
  break;
494
-
495
- case 'ai':
496
- const question = args.join(' ');
497
- if (!question) {
498
- await sock.sendMessage(sender, {
499
- text: '❌ Ketik: !ai pertanyaan lu'
500
- });
501
- return;
502
- }
503
- await handleAIResponse(sock, sender, question);
504
  break;
505
-
 
 
 
 
 
506
  default:
507
- await sock.sendMessage(sender, {
508
- text: '❓ Command tidak dikenal. Ketik !help'
509
- });
510
  }
511
  }
512
 
513
- // ===== AI RESPONSE (Hugging Face) =====
514
  async function handleAIResponse(sock, sender, text) {
515
  try {
516
- await sock.sendMessage(sender, { text: '⏳ AI sedang berpikir...' });
517
 
518
- // Pake model gratis dari Hugging Face
519
  const response = await axios.post(
520
  'https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2',
521
  {
@@ -523,7 +890,7 @@ async function handleAIResponse(sock, sender, text) {
523
  parameters: { max_new_tokens: 500, temperature: 0.7 }
524
  },
525
  {
526
- headers: {
527
  'Authorization': `Bearer ${HF_TOKEN}`,
528
  'Content-Type': 'application/json'
529
  },
@@ -531,29 +898,27 @@ async function handleAIResponse(sock, sender, text) {
531
  }
532
  );
533
 
534
- let reply = '';
535
- if (response.data && response.data[0]) {
536
- reply = response.data[0].generated_text || 'Tidak ada response';
537
- // Bersihin format
538
- reply = reply.replace(/<s>\[INST\].*?\[\/INST\]\s*/, '');
539
- } else {
540
- reply = 'Maaf, AI tidak merespon.';
541
- }
542
-
543
- await sock.sendMessage(sender, { text: `🤖 *AI:*\n\n${reply}` });
544
 
545
  } catch (error) {
546
- console.error('AI Error:', error.message);
547
  await sock.sendMessage(sender, {
548
- text: '❌ Error AI: ' + (error.response?.data?.error || 'Service sibuk, coba lagi')
549
  });
550
  }
551
  }
552
 
553
- // ===== START EVERYTHING =====
554
- startBot();
 
 
 
 
555
 
556
- // Keep alive log
557
  setInterval(() => {
558
- console.log(`[${new Date().toISOString()}] 💓 Heartbeat - Status: ${connectionStatus}`);
559
  }, 60000);
 
2
  default: makeWASocket,
3
  DisconnectReason,
4
  useMultiFileAuthState,
5
+ fetchLatestBaileysVersion,
6
+ usePairingCode
7
  } = require('@whiskeysockets/baileys');
8
  const QRCode = require('qrcode-terminal');
9
  const qrcode = require('qrcode');
10
  const axios = require('axios');
11
  const fs = require('fs');
12
  const path = require('path');
13
+ const express = require('express');
14
  const http = require('http');
15
+ const WebSocket = require('ws');
16
+
17
+ const app = express();
18
+ const server = http.createServer(app);
19
+ const wss = new WebSocket.Server({ server });
20
 
21
  // ===== CONFIG =====
22
  const PORT = process.env.PORT || 7860;
23
  const HF_TOKEN = process.env.HF_TOKEN || '';
24
+ const AUTH_FOLDER = path.join(__dirname, 'auth');
25
+ const USE_PAIRING_CODE = true; // Set false kalau mau QR biasa
26
 
27
+ // State
28
  let sock = null;
29
  let qrCodeData = null;
30
+ let pairingCode = null;
31
  let connectionStatus = 'disconnected';
32
+ let connectionLogs = [];
33
+ let startTime = Date.now();
34
 
35
+ // ===== MIDDLEWARE =====
36
+ app.use(express.json());
37
+ app.use(express.static('public'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ // ===== HTML UI KEREN =====
40
+ const HTML_UI = `
 
41
  <!DOCTYPE html>
42
  <html lang="id">
43
  <head>
44
  <meta charset="UTF-8">
45
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
46
+ <title>🤖 WhatsApp Bot - HF Space</title>
47
  <style>
48
  * { margin: 0; padding: 0; box-sizing: border-box; }
49
+
50
  body {
51
+ font-family: 'Segoe UI', system-ui, sans-serif;
52
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
53
  min-height: 100vh;
54
+ color: #fff;
55
+ overflow-x: hidden;
56
+ }
57
+
58
+ .stars {
59
+ position: fixed;
60
+ top: 0; left: 0; width: 100%; height: 100%;
61
+ pointer-events: none;
62
+ background-image:
63
+ radial-gradient(2px 2px at 20px 30px, #eee, rgba(0,0,0,0)),
64
+ radial-gradient(2px 2px at 40px 70px, #fff, rgba(0,0,0,0)),
65
+ radial-gradient(2px 2px at 50px 160px, #ddd, rgba(0,0,0,0)),
66
+ radial-gradient(2px 2px at 90px 40px, #fff, rgba(0,0,0,0)),
67
+ radial-gradient(2px 2px at 130px 80px, #fff, rgba(0,0,0,0));
68
+ background-repeat: repeat;
69
+ background-size: 200px 200px;
70
+ animation: twinkle 5s ease-in-out infinite;
71
+ opacity: 0.5;
72
+ }
73
+
74
+ @keyframes twinkle {
75
+ 0%, 100% { opacity: 0.5; }
76
+ 50% { opacity: 0.8; }
77
  }
78
+
79
  .container {
80
+ max-width: 900px;
81
  margin: 0 auto;
82
+ padding: 20px;
83
+ position: relative;
84
+ z-index: 1;
85
  }
86
+
87
+ header {
88
+ text-align: center;
89
+ padding: 40px 0;
90
+ }
91
+
92
+ h1 {
93
+ font-size: 3em;
94
+ background: linear-gradient(90deg, #00d4ff, #7b2cbf, #ff006e);
95
+ -webkit-background-clip: text;
96
+ -webkit-text-fill-color: transparent;
97
+ background-clip: text;
98
+ margin-bottom: 10px;
99
+ animation: gradient 3s ease infinite;
100
+ background-size: 200% 200%;
101
+ }
102
+
103
+ @keyframes gradient {
104
+ 0% { background-position: 0% 50%; }
105
+ 50% { background-position: 100% 50%; }
106
+ 100% { background-position: 0% 50%; }
107
+ }
108
+
109
+ .subtitle {
110
+ color: #8892b0;
111
+ font-size: 1.1em;
112
+ }
113
+
114
+ .status-card {
115
+ background: rgba(255,255,255,0.05);
116
  backdrop-filter: blur(10px);
117
+ border: 1px solid rgba(255,255,255,0.1);
118
+ border-radius: 24px;
119
+ padding: 30px;
120
+ margin-bottom: 25px;
121
+ box-shadow: 0 8px 32px rgba(0,0,0,0.3);
122
  }
123
+
124
+ .status-header {
125
  display: flex;
126
  align-items: center;
127
+ gap: 20px;
128
+ margin-bottom: 25px;
129
  }
130
+
131
  .status-icon {
132
+ width: 80px; height: 80px;
133
  border-radius: 50%;
134
  display: flex; align-items: center; justify-content: center;
135
+ font-size: 36px;
136
+ position: relative;
137
+ transition: all 0.3s;
138
+ }
139
+
140
+ .status-icon::before {
141
+ content: '';
142
+ position: absolute;
143
+ inset: -5px;
144
+ border-radius: 50%;
145
+ background: inherit;
146
+ opacity: 0.3;
147
+ animation: pulse-ring 2s ease-out infinite;
148
+ }
149
+
150
+ @keyframes pulse-ring {
151
+ 0% { transform: scale(1); opacity: 0.5; }
152
+ 100% { transform: scale(1.3); opacity: 0; }
153
  }
154
+
155
+ .connected { background: linear-gradient(135deg, #00b894, #00cec9); }
156
+ .disconnected { background: linear-gradient(135deg, #e17055, #d63031); }
157
+ .connecting { background: linear-gradient(135deg, #fdcb6e, #e17055); animation: pulse 1.5s infinite; }
158
+
159
  @keyframes pulse {
160
  0%, 100% { transform: scale(1); }
161
  50% { transform: scale(1.05); }
162
  }
163
+
164
+ .status-info h2 {
165
+ font-size: 1.8em;
166
+ margin-bottom: 5px;
167
+ }
168
+
169
+ .status-badge {
170
+ display: inline-block;
171
+ padding: 6px 16px;
172
+ border-radius: 20px;
173
+ font-size: 12px;
174
+ font-weight: bold;
175
+ text-transform: uppercase;
176
+ letter-spacing: 1px;
177
+ }
178
+
179
+ .badge-online { background: #00b894; color: #fff; }
180
+ .badge-offline { background: #e17055; color: #fff; }
181
+ .badge-connecting { background: #fdcb6e; color: #000; }
182
+
183
+ .connection-panel {
184
+ background: rgba(0,0,0,0.2);
185
+ border-radius: 16px;
186
+ padding: 25px;
187
  text-align: center;
188
+ }
189
+
190
+ .pairing-box {
191
+ background: linear-gradient(135deg, rgba(0,212,255,0.1), rgba(123,44,191,0.1));
192
+ border: 2px dashed #00d4ff;
193
+ border-radius: 16px;
194
+ padding: 30px;
195
+ margin: 20px 0;
196
+ }
197
+
198
+ .pairing-code {
199
+ font-family: 'Courier New', monospace;
200
+ font-size: 3em;
201
+ font-weight: bold;
202
+ color: #00d4ff;
203
+ letter-spacing: 10px;
204
+ text-shadow: 0 0 20px rgba(0,212,255,0.5);
205
+ margin: 15px 0;
206
+ animation: glow 2s ease-in-out infinite alternate;
207
+ }
208
+
209
+ @keyframes glow {
210
+ from { text-shadow: 0 0 20px rgba(0,212,255,0.5); }
211
+ to { text-shadow: 0 0 30px rgba(0,212,255,0.8), 0 0 40px rgba(0,212,255,0.4); }
212
+ }
213
+
214
+ .qr-box {
215
+ background: #fff;
216
  padding: 20px;
217
+ border-radius: 16px;
218
+ display: inline-block;
219
+ margin: 15px 0;
220
  }
221
+
222
+ .qr-box img {
223
+ width: 280px; height: 280px;
224
+ }
225
+
226
+ .instructions {
227
+ text-align: left;
228
+ background: rgba(255,255,255,0.05);
229
+ border-radius: 12px;
230
+ padding: 20px;
231
+ margin-top: 20px;
232
  }
233
+
234
+ .instructions h3 {
235
+ color: #00d4ff;
236
+ margin-bottom: 15px;
237
+ display: flex; align-items: center; gap: 10px;
238
+ }
239
+
240
+ .instructions ol {
241
+ padding-left: 20px;
242
+ line-height: 2;
243
+ color: #a8b2d1;
244
+ }
245
+
246
+ .instructions li {
247
+ margin-bottom: 8px;
248
+ }
249
+
250
  .btn {
251
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
252
+ color: white;
253
  border: none;
254
+ padding: 15px 40px;
255
+ border-radius: 30px;
256
  font-size: 16px;
257
  font-weight: bold;
258
  cursor: pointer;
 
259
  transition: all 0.3s;
260
+ margin: 10px;
261
+ text-transform: uppercase;
262
+ letter-spacing: 1px;
263
  }
264
+
265
+ .btn:hover {
266
+ transform: translateY(-3px);
267
+ box-shadow: 0 10px 30px rgba(102,126,234,0.4);
 
 
 
 
 
 
268
  }
269
+
270
+ .btn-danger {
271
+ background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
272
+ }
273
+
274
+ .stats-grid {
275
+ display: grid;
276
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
277
+ gap: 15px;
278
+ margin-top: 20px;
279
+ }
280
+
281
+ .stat-box {
282
+ background: rgba(255,255,255,0.05);
283
+ border-radius: 12px;
284
+ padding: 20px;
285
+ text-align: center;
286
+ border: 1px solid rgba(255,255,255,0.1);
287
+ }
288
+
289
+ .stat-value {
290
+ font-size: 2em;
291
  font-weight: bold;
292
+ color: #00d4ff;
293
+ }
294
+
295
+ .stat-label {
296
+ color: #8892b0;
297
+ font-size: 0.9em;
298
+ margin-top: 5px;
299
  }
300
+
301
+ .logs-container {
302
+ background: rgba(0,0,0,0.4);
303
+ border-radius: 12px;
304
  padding: 15px;
305
+ margin-top: 20px;
 
 
306
  max-height: 200px;
307
  overflow-y: auto;
308
+ font-family: 'Courier New', monospace;
309
+ font-size: 12px;
310
+ line-height: 1.6;
311
+ }
312
+
313
+ .log-entry {
314
+ padding: 3px 0;
315
+ border-bottom: 1px solid rgba(255,255,255,0.05);
316
+ }
317
+
318
+ .log-time { color: #00d4ff; }
319
+ .log-info { color: #a8b2d1; }
320
+ .log-success { color: #00b894; }
321
+ .log-error { color: #e17055; }
322
+
323
+ .features {
324
+ display: grid;
325
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
326
+ gap: 20px;
327
+ margin-top: 30px;
328
+ }
329
+
330
+ .feature-card {
331
+ background: rgba(255,255,255,0.03);
332
+ border: 1px solid rgba(255,255,255,0.1);
333
+ border-radius: 16px;
334
+ padding: 25px;
335
+ transition: all 0.3s;
336
+ }
337
+
338
+ .feature-card:hover {
339
+ transform: translateY(-5px);
340
+ background: rgba(255,255,255,0.08);
341
+ border-color: rgba(0,212,255,0.3);
342
+ }
343
+
344
+ .feature-icon {
345
+ font-size: 2.5em;
346
+ margin-bottom: 15px;
347
+ }
348
+
349
+ .feature-title {
350
+ font-size: 1.2em;
351
+ margin-bottom: 10px;
352
+ color: #00d4ff;
353
+ }
354
+
355
+ .feature-desc {
356
+ color: #8892b0;
357
+ font-size: 0.95em;
358
+ line-height: 1.6;
359
+ }
360
+
361
+ footer {
362
+ text-align: center;
363
+ padding: 40px 0;
364
+ color: #8892b0;
365
+ font-size: 0.9em;
366
+ }
367
+
368
+ .hidden { display: none !important; }
369
+
370
+ @media (max-width: 600px) {
371
+ h1 { font-size: 2em; }
372
+ .pairing-code { font-size: 2em; letter-spacing: 5px; }
373
+ .qr-box img { width: 200px; height: 200px; }
374
  }
375
  </style>
376
  </head>
377
  <body>
378
+ <div class="stars"></div>
379
+
380
  <div class="container">
381
+ <header>
382
+ <h1>🚀 WhatsApp AI Bot</h1>
383
+ <p class="subtitle">Powered by Hugging Face Space</p>
384
+ </header>
385
+
386
+ <!-- STATUS CARD -->
387
+ <div class="status-card">
388
+ <div class="status-header">
389
+ <div class="status-icon disconnected" id="statusIcon">📴</div>
390
+ <div class="status-info">
391
+ <h2 id="statusTitle">Menghubungkan...</h2>
392
+ <span class="status-badge badge-offline" id="statusBadge">Offline</span>
393
  </div>
394
  </div>
395
 
396
+ <!-- CONNECTING PANEL -->
397
+ <div id="connectingPanel" class="connection-panel">
398
+ <p>Sedang menyiapkan koneksi...</p>
399
+ <div class="stats-grid" style="margin-top: 20px;">
400
+ <div class="stat-box">
401
+ <div class="stat-value" id="uptime">0s</div>
402
+ <div class="stat-label">Uptime</div>
403
+ </div>
404
+ <div class="stat-box">
405
+ <div class="stat-value" id="reconnectCount">0</div>
406
+ <div class="stat-label">Reconnect</div>
407
+ </div>
408
+ </div>
409
  </div>
410
 
411
+ <!-- PAIRING CODE PANEL -->
412
+ <div id="pairingPanel" class="connection-panel hidden">
413
+ <div class="pairing-box">
414
+ <h3>📱 Pairing Code</h3>
415
+ <p style="margin-bottom: 15px; color: #a8b2d1;">Masukkan kode ini di WhatsApp</p>
416
+ <div class="pairing-code" id="pairingCode">----</div>
417
+ <p style="margin-top: 15px; font-size: 0.9em; color: #fdcb6e;">⏳ Berlaku 2 menit</p>
418
  </div>
419
+
420
+ <div class="instructions">
421
+ <h3>📋 Cara Connect:</h3>
422
+ <ol>
423
+ <li>Buka <strong>WhatsApp Android</strong> lu</li>
424
+ <li>Klik <strong>⋮ (Menu)</strong> → <strong>Linked Devices</strong></li>
425
+ <li>Klik <strong>Link a Device</strong></li>
426
+ <li>Pilih <strong>Link with phone number</strong></li>
427
+ <li>Masukkan kode di atas ☝️</li>
428
+ <li>Tunggu sampai terhubung!</li>
429
+ </ol>
430
+ </div>
431
+
432
+ <button class="btn" onclick="switchToQR()">🔄 Ganti ke QR Code</button>
433
  </div>
434
 
435
+ <!-- QR PANEL -->
436
+ <div id="qrPanel" class="connection-panel hidden">
437
+ <div class="qr-box">
438
+ <img id="qrImage" src="" alt="QR Code">
439
+ </div>
440
+ <p style="margin-top: 15px; color: #a8b2d1;">Scan pakai WhatsApp → Linked Devices</p>
441
+ <button class="btn" onclick="switchToPairing()">🔄 Ganti ke Pairing Code</button>
442
+ </div>
443
+
444
+ <!-- CONNECTED PANEL -->
445
+ <div id="connectedPanel" class="connection-panel hidden">
446
+ <div style="font-size: 4em; margin-bottom: 15px;">✅</div>
447
+ <h3 style="color: #00b894; margin-bottom: 10px;">Bot Terhubung!</h3>
448
+ <p style="color: #a8b2d1; margin-bottom: 20px;">Siap menerima pesan WhatsApp</p>
449
 
450
+ <div class="stats-grid">
451
+ <div class="stat-box">
452
+ <div class="stat-value" id="connUptime">0s</div>
453
+ <div class="stat-label">Uptime</div>
454
+ </div>
455
+ <div class="stat-box">
456
+ <div class="stat-value" id="msgReceived">0</div>
457
+ <div class="stat-label">Pesan</div>
458
+ </div>
459
+ <div class="stat-box">
460
+ <div class="stat-value" id="msgSent">0</div>
461
+ <div class="stat-label">Terbalas</div>
462
+ </div>
463
+ </div>
464
 
465
+ <button class="btn btn-danger" onclick="logout()" style="margin-top: 20px;">🚪 Logout & Reset</button>
466
+ </div>
467
+
468
+ <div class="logs-container" id="logs">
469
+ <div class="log-entry"><span class="log-time">[--:--:--]</span> <span class="log-info">System initialized...</span></div>
470
+ </div>
471
+ </div>
472
+
473
+ <!-- FEATURES -->
474
+ <div class="features">
475
+ <div class="feature-card">
476
+ <div class="feature-icon">🤖</div>
477
+ <div class="feature-title">AI Chat</div>
478
+ <div class="feature-desc">Balas pesan otomatis pakai AI Hugging Face. Tinggal chat langsung dapet jawaban!</div>
479
+ </div>
480
+ <div class="feature-card">
481
+ <div class="feature-icon">🖼️</div>
482
+ <div class="feature-title">Image Analysis</div>
483
+ <div class="feature-desc">Kirim foto ke bot, AI bakal analyze dan deskripsiin isi gambarnya.</div>
484
+ </div>
485
+ <div class="feature-card">
486
+ <div class="feature-icon">⚡</div>
487
+ <div class="feature-title">24/7 Online</div>
488
+ <div class="feature-desc">Jalan terus di cloud Hugging Face. Auto-reconnect kalau terputus.</div>
489
+ </div>
490
+ <div class="feature-card">
491
+ <div class="feature-icon">🔒</div>
492
+ <div class="feature-title">Secure Session</div>
493
+ <div class="feature-desc">Session tersimpan aman. Login sekali, tetap connect selamanya.</div>
494
  </div>
495
  </div>
496
+
497
+ <footer>
498
+ <p>🚀 Running on Hugging Face Space | Built with Baileys & Node.js</p>
499
+ <p style="margin-top: 10px; font-size: 0.8em;">Status: <span id="footerStatus">Initializing</span></p>
500
+ </footer>
501
  </div>
502
 
503
  <script>
504
+ let currentMode = 'pairing'; // 'pairing' atau 'qr'
505
+ let stats = { received: 0, sent: 0, startTime: Date.now() };
506
+
507
+ function addLog(msg, type = 'info') {
508
+ const logs = document.getElementById('logs');
509
+ const time = new Date().toLocaleTimeString();
510
+ const typeClass = type === 'success' ? 'log-success' : type === 'error' ? 'log-error' : 'log-info';
511
+ logs.innerHTML += \`<div class="log-entry"><span class="log-time">[\${time}]</span> <span class="\${typeClass}">\${msg}</span></div>\`;
512
+ logs.scrollTop = logs.scrollHeight;
513
+ }
514
+
515
+ function updateUptime() {
516
+ const elapsed = Math.floor((Date.now() - stats.startTime) / 1000);
517
+ const hours = Math.floor(elapsed / 3600);
518
+ const mins = Math.floor((elapsed % 3600) / 60);
519
+ const secs = elapsed % 60;
520
+ const str = hours > 0 ? \`\${hours}j \${mins}m\` : \`\${mins}m \${secs}s\`;
521
+
522
+ document.getElementById('uptime').textContent = str;
523
+ document.getElementById('connUptime').textContent = str;
524
+ }
525
+
526
+ setInterval(updateUptime, 1000);
527
 
528
  async function checkStatus() {
529
  try {
530
+ const res = await fetch('/api/status');
531
  const data = await res.json();
 
532
  updateUI(data);
533
  } catch (e) {
534
+ addLog('Connection error: ' + e.message, 'error');
535
  }
536
  }
537
 
538
  function updateUI(data) {
539
+ const status = data.status;
540
  const icon = document.getElementById('statusIcon');
541
  const title = document.getElementById('statusTitle');
542
+ const badge = document.getElementById('statusBadge');
543
+ const footer = document.getElementById('footerStatus');
 
 
544
 
545
  // Update status display
546
+ if (status === 'connected') {
547
  icon.className = 'status-icon connected';
548
  icon.textContent = '✓';
549
  title.textContent = 'Terhubung';
550
+ badge.className = 'status-badge badge-online';
551
+ badge.textContent = 'Online';
552
+ footer.textContent = 'Connected';
553
+
554
+ showPanel('connectedPanel');
555
+ stats.received = data.messagesReceived || 0;
556
+ stats.sent = data.messagesSent || 0;
557
+ document.getElementById('msgReceived').textContent = stats.received;
558
+ document.getElementById('msgSent').textContent = stats.sent;
559
+
560
+ } else if (status === 'qr_ready') {
561
+ icon.className = 'status-icon connecting';
562
  icon.textContent = '📱';
563
+ title.textContent = 'Menunggu Scan';
564
+ badge.className = 'status-badge badge-connecting';
565
+ badge.textContent = 'QR Ready';
566
+ footer.textContent = 'Waiting for QR scan';
567
+
568
+ if (currentMode === 'qr') {
569
+ showPanel('qrPanel');
570
+ document.getElementById('qrImage').src = '/api/qr?' + Date.now();
571
+ } else {
572
+ showPanel('pairingPanel');
573
+ }
574
+
575
+ } else if (status === 'pairing_code') {
576
+ icon.className = 'status-icon connecting';
577
+ icon.textContent = '🔢';
578
+ title.textContent = 'Menunggu Pairing';
579
+ badge.className = 'status-badge badge-connecting';
580
+ badge.textContent = 'Pairing';
581
+ footer.textContent = 'Waiting for pairing code';
582
+
583
+ showPanel('pairingPanel');
584
+ if (data.pairingCode) {
585
+ document.getElementById('pairingCode').textContent = data.pairingCode;
586
+ }
587
+
588
+ } else {
589
  icon.className = 'status-icon disconnected';
590
+ icon.textContent = '';
591
+ title.textContent = 'Menghubungkan...';
592
+ badge.className = 'status-badge badge-offline';
593
+ badge.textContent = 'Connecting';
594
+ footer.textContent = 'Reconnecting...';
595
+
596
+ showPanel('connectingPanel');
597
  }
598
  }
599
 
600
+ function showPanel(id) {
601
+ ['connectingPanel', 'pairingPanel', 'qrPanel', 'connectedPanel'].forEach(pid => {
602
+ document.getElementById(pid).classList.add('hidden');
603
+ });
604
+ document.getElementById(id).classList.remove('hidden');
605
+ }
606
+
607
+ function switchToQR() {
608
+ currentMode = 'qr';
609
+ fetch('/api/switch-mode?mode=qr');
610
+ addLog('Switched to QR mode');
611
  }
612
 
613
+ function switchToPairing() {
614
+ currentMode = 'pairing';
615
+ fetch('/api/switch-mode?mode=pairing');
616
+ addLog('Switched to pairing code mode');
617
+ }
618
+
619
+ async function logout() {
620
+ if (!confirm('Yakin mau logout? Perlu pair ulang.')) return;
621
+ addLog('Logging out...');
622
+ await fetch('/api/logout');
623
  setTimeout(checkStatus, 2000);
624
  }
625
 
626
+ // WebSocket untuk real-time updates
627
+ const ws = new WebSocket('ws://' + window.location.host);
628
+ ws.onmessage = (event) => {
629
+ const data = JSON.parse(event.data);
630
+ if (data.type === 'log') addLog(data.message, data.logType);
631
+ if (data.type === 'status') updateUI(data.data);
632
+ };
633
+
634
+ // Polling fallback
635
+ setInterval(checkStatus, 3000);
636
  checkStatus();
637
+ addLog('System ready', 'success');
 
638
  </script>
639
  </body>
640
  </html>
641
+ `;
 
642
 
643
+ // ===== ROUTES =====
644
+
645
+ // Main UI
646
+ app.get('/', (req, res) => res.send(HTML_UI));
647
+ app.get('/panel', (req, res) => res.send(HTML_UI));
648
+
649
+ // API Status
650
+ app.get('/api/status', (req, res) => {
651
+ res.json({
652
+ status: connectionStatus,
653
+ pairingCode: pairingCode,
654
+ qrReady: !!qrCodeData,
655
+ uptime: Math.floor((Date.now() - startTime) / 1000),
656
+ timestamp: new Date().toISOString()
657
+ });
658
+ });
659
+
660
+ // Get QR Image
661
+ app.get('/api/qr', async (req, res) => {
662
+ if (qrCodeData) {
663
+ const buffer = await qrcode.toBuffer(qrCodeData, {
664
+ type: 'png', width: 400, margin: 2
665
+ });
666
+ res.set('Content-Type', 'image/png');
667
+ res.send(buffer);
668
  } else {
669
+ res.status(404).json({ error: 'QR not ready' });
 
670
  }
671
+ });
672
+
673
+ // Switch mode
674
+ app.get('/api/switch-mode', (req, res) => {
675
+ const mode = req.query.mode;
676
+ // Implementasi switch mode bisa ditambahin di sini
677
+ res.json({ success: true, mode });
678
+ });
679
 
680
+ // Logout
681
+ app.get('/api/logout', async (req, res) => {
682
+ if (sock) {
683
+ await sock.logout();
684
+ }
685
+ // Hapus session
686
+ if (fs.existsSync(AUTH_FOLDER)) {
687
+ fs.rmSync(AUTH_FOLDER, { recursive: true });
688
+ }
689
+ res.json({ success: true });
690
+ });
691
+
692
+ // ===== WEBSOCKET BROADCAST =====
693
+ function broadcast(data) {
694
+ wss.clients.forEach(client => {
695
+ if (client.readyState === WebSocket.OPEN) {
696
+ client.send(JSON.stringify(data));
697
+ }
698
+ });
699
+ }
700
+
701
+ function log(message, type = 'info') {
702
+ console.log(`[${new Date().toLocaleTimeString()}] ${message}`);
703
+ broadcast({ type: 'log', message, logType: type });
704
+ }
705
+
706
+ // ===== WHATSAPP BOT =====
707
+ async function startBot() {
708
+ log('🚀 Starting WhatsApp Bot...');
709
+
710
  const { state, saveCreds } = await useMultiFileAuthState(AUTH_FOLDER);
711
  const { version } = await fetchLatestBaileysVersion();
712
 
713
+ // Pilih pairing method
714
+ const phoneNumber = ''; // Kosongin biar pake QR atau isi nomor lu buat pairing code
715
+
716
  sock = makeWASocket({
717
  version,
718
+ logger: require('pino')({ level: 'silent' }),
719
  printQRInTerminal: true,
720
  auth: state,
721
  browser: ['Ubuntu', 'Chrome', '20.0.04'],
722
  generateHighQualityLinkPreview: true,
723
  syncFullHistory: false,
724
  markOnlineOnConnect: true,
725
+ keepAliveIntervalMs: 30000,
726
  connectTimeoutMs: 60000,
727
  defaultQueryTimeoutMs: 60000,
728
+ // Pairing code config
729
+ pairingCode: USE_PAIRING_CODE,
730
  });
731
 
732
+ // Pairing code handler
733
+ if (USE_PAIRING_CODE && !state.creds.registered && phoneNumber) {
734
+ setTimeout(async () => {
735
+ const code = await sock.requestPairingCode(phoneNumber);
736
+ pairingCode = code;
737
+ connectionStatus = 'pairing_code';
738
+ log(`🔢 Pairing Code: ${code}`, 'success');
739
+ log('📱 Masukkan kode di WhatsApp → Linked Devices', 'info');
740
+ }, 3000);
741
+ }
742
+
743
  // Event handlers
744
  sock.ev.on('connection.update', async (update) => {
745
  const { connection, lastDisconnect, qr } = update;
746
 
 
747
  if (qr) {
 
 
 
748
  qrCodeData = qr;
 
749
  connectionStatus = 'qr_ready';
750
+ log('📷 QR Code ready! Scan sekarang...', 'info');
751
+ broadcast({ type: 'status', data: { status: 'qr_ready' } });
 
752
  }
753
 
 
754
  if (connection === 'open') {
 
 
 
 
755
  connectionStatus = 'connected';
756
  qrCodeData = null;
757
+ pairingCode = null;
758
+ log('✅ BOT CONNECTED!', 'success');
759
+ log(`📱 Nomor: ${sock.user.id.split(':')[0]}`, 'info');
760
+
761
+ broadcast({
762
+ type: 'status',
763
+ data: {
764
+ status: 'connected',
765
+ user: sock.user.name || sock.user.id
766
+ }
767
+ });
768
 
769
+ // Notifikasi startup
770
  try {
771
  await sock.sendMessage(sock.user.id, {
772
+ text: `🤖 *Bot Aktif!*\n\n✅ Terhubung ke Hugging Face Space\n⏰ ${new Date().toLocaleString()}\n\nKetik *!help* untuk bantuan.`
773
  });
774
+ } catch (e) {}
 
 
775
  }
776
 
 
777
  else if (connection === 'close') {
778
  const statusCode = lastDisconnect?.error?.output?.statusCode;
779
  const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
780
 
 
781
  connectionStatus = 'disconnected';
782
+ log(`❌ Disconnected: ${statusCode}`, 'error');
783
+ broadcast({ type: 'status', data: { status: 'disconnected' } });
784
 
785
  if (shouldReconnect) {
786
+ log('🔄 Reconnecting in 5s...', 'info');
787
  setTimeout(startBot, 5000);
 
 
788
  }
789
  }
790
  });
791
 
 
792
  sock.ev.on('creds.update', saveCreds);
793
 
794
+ // Message handler
795
+ let msgCount = { received: 0, sent: 0 };
796
+
797
  sock.ev.on('messages.upsert', async ({ messages, type }) => {
798
  if (type !== 'notify') return;
799
 
800
  const msg = messages[0];
801
  if (!msg.message || msg.key.fromMe) return;
802
 
803
+ msgCount.received++;
804
  const sender = msg.key.remoteJid;
805
+ const pushName = msg.pushName || 'User';
 
806
 
807
+ let text = msg.message.conversation ||
808
+ msg.message.extendedTextMessage?.text ||
809
+ msg.message.imageMessage?.caption || '';
 
 
 
 
 
 
810
 
811
+ log(`📩 ${pushName}: ${text.substring(0, 30)}...`);
 
812
 
813
+ // Commands
814
  if (text.startsWith('!')) {
815
+ await handleCommand(sock, msg, text, sender);
816
+ msgCount.sent++;
817
  }
818
+ // Auto reply private chat
819
+ else if (!sender.endsWith('@g.us') && text) {
820
  await handleAIResponse(sock, sender, text);
821
+ msgCount.sent++;
822
  }
823
+
824
+ broadcast({
825
+ type: 'status',
826
+ data: {
827
+ status: connectionStatus,
828
+ messagesReceived: msgCount.received,
829
+ messagesSent: msgCount.sent
830
+ }
831
+ });
832
  });
833
  }
834
 
835
+ // ===== COMMANDS =====
836
+ async function handleCommand(sock, msg, text, sender) {
837
+ const cmd = text.slice(1).split(' ')[0].toLowerCase();
838
+ const args = text.slice(1).split(' ').slice(1).join(' ');
839
 
840
  switch(cmd) {
841
  case 'help':
842
  await sock.sendMessage(sender, {
843
+ text: `🤖 *Command List*\n\n` +
844
+ `*!ai [pertanyaan]* - Tanya AI\n` +
845
+ `*!status* - Cek status bot\n` +
846
+ `*!ping* - Test bot\n` +
847
+ `*!image [prompt]* - Generate gambar\n\n` +
848
+ `Atau langsung chat aja buat AI auto-reply!`
849
  });
850
  break;
851
+
852
+ case 'ai':
853
+ if (!args) {
854
+ await sock.sendMessage(sender, { text: '❌ Ketik: !ai pertanyaan lu' });
855
+ return;
856
+ }
857
+ await handleAIResponse(sock, sender, args);
858
  break;
859
+
860
  case 'status':
861
+ const uptime = Math.floor((Date.now() - startTime) / 1000);
 
 
862
  await sock.sendMessage(sender, {
863
+ text: `📊 *Status*\nUptime: ${Math.floor(uptime/60)}m\nStatus: ${connectionStatus}`
 
 
 
864
  });
865
  break;
866
+
867
+ case 'ping':
868
+ await sock.sendMessage(sender, { text: '🏓 Pong! Bot aktif.' });
 
 
 
 
 
 
 
869
  break;
870
+
871
+ case 'image':
872
+ await sock.sendMessage(sender, { text: '⏳ Generating image...' });
873
+ // Implementasi image generation bisa ditambahin
874
+ break;
875
+
876
  default:
877
+ await sock.sendMessage(sender, { text: '❓ Command gak dikenal. Ketik !help' });
 
 
878
  }
879
  }
880
 
881
+ // ===== AI RESPONSE =====
882
  async function handleAIResponse(sock, sender, text) {
883
  try {
884
+ await sock.sendMessage(sender, { text: '⏳ AI mikir...' });
885
 
 
886
  const response = await axios.post(
887
  'https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2',
888
  {
 
890
  parameters: { max_new_tokens: 500, temperature: 0.7 }
891
  },
892
  {
893
+ headers: {
894
  'Authorization': `Bearer ${HF_TOKEN}`,
895
  'Content-Type': 'application/json'
896
  },
 
898
  }
899
  );
900
 
901
+ let reply = response.data[0]?.generated_text || 'Gagal dapet response';
902
+ reply = reply.replace(/<s>\[INST\].*?\[\/INST\]\s*/, '');
903
+
904
+ await sock.sendMessage(sender, { text: `🤖 ${reply}` });
 
 
 
 
 
 
905
 
906
  } catch (error) {
907
+ log('AI Error: ' + error.message, 'error');
908
  await sock.sendMessage(sender, {
909
+ text: '❌ AI lagi sibuk, coba lagi ntar ya.'
910
  });
911
  }
912
  }
913
 
914
+ // ===== START =====
915
+ server.listen(PORT, () => {
916
+ log(`🌐 Web server: http://localhost:${PORT}`, 'success');
917
+ log(`📱 Panel: http://localhost:${PORT}/panel`, 'info');
918
+ startBot();
919
+ });
920
 
921
+ // Keep alive
922
  setInterval(() => {
923
+ log(`💓 Heartbeat | Status: ${connectionStatus}`, 'info');
924
  }, 60000);