@kangwifi-pro/waliwa 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +600 -0
  3. package/dist/advanced/features.d.ts +211 -0
  4. package/dist/advanced/features.d.ts.map +1 -0
  5. package/dist/advanced/features.js +671 -0
  6. package/dist/advanced/features.js.map +1 -0
  7. package/dist/auth/auth-state.d.ts +89 -0
  8. package/dist/auth/auth-state.d.ts.map +1 -0
  9. package/dist/auth/auth-state.js +330 -0
  10. package/dist/auth/auth-state.js.map +1 -0
  11. package/dist/auth/pairing-code.d.ts +78 -0
  12. package/dist/auth/pairing-code.d.ts.map +1 -0
  13. package/dist/auth/pairing-code.js +191 -0
  14. package/dist/auth/pairing-code.js.map +1 -0
  15. package/dist/auth/qr-code.d.ts +60 -0
  16. package/dist/auth/qr-code.d.ts.map +1 -0
  17. package/dist/auth/qr-code.js +151 -0
  18. package/dist/auth/qr-code.js.map +1 -0
  19. package/dist/calls/handler.d.ts +61 -0
  20. package/dist/calls/handler.d.ts.map +1 -0
  21. package/dist/calls/handler.js +117 -0
  22. package/dist/calls/handler.js.map +1 -0
  23. package/dist/core/binary.d.ts +74 -0
  24. package/dist/core/binary.d.ts.map +1 -0
  25. package/dist/core/binary.js +448 -0
  26. package/dist/core/binary.js.map +1 -0
  27. package/dist/core/crypto.d.ts +60 -0
  28. package/dist/core/crypto.d.ts.map +1 -0
  29. package/dist/core/crypto.js +170 -0
  30. package/dist/core/crypto.js.map +1 -0
  31. package/dist/core/noise.d.ts +106 -0
  32. package/dist/core/noise.d.ts.map +1 -0
  33. package/dist/core/noise.js +307 -0
  34. package/dist/core/noise.js.map +1 -0
  35. package/dist/events/emitter.d.ts +44 -0
  36. package/dist/events/emitter.d.ts.map +1 -0
  37. package/dist/events/emitter.js +79 -0
  38. package/dist/events/emitter.js.map +1 -0
  39. package/dist/features/index.d.ts +342 -0
  40. package/dist/features/index.d.ts.map +1 -0
  41. package/dist/features/index.js +755 -0
  42. package/dist/features/index.js.map +1 -0
  43. package/dist/fixes/index.d.ts +333 -0
  44. package/dist/fixes/index.d.ts.map +1 -0
  45. package/dist/fixes/index.js +762 -0
  46. package/dist/fixes/index.js.map +1 -0
  47. package/dist/groups/management.d.ts +86 -0
  48. package/dist/groups/management.d.ts.map +1 -0
  49. package/dist/groups/management.js +443 -0
  50. package/dist/groups/management.js.map +1 -0
  51. package/dist/index.d.ts +65 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +236 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/messages/media.d.ts +93 -0
  56. package/dist/messages/media.d.ts.map +1 -0
  57. package/dist/messages/media.js +252 -0
  58. package/dist/messages/media.js.map +1 -0
  59. package/dist/messages/message-encoder.d.ts +70 -0
  60. package/dist/messages/message-encoder.d.ts.map +1 -0
  61. package/dist/messages/message-encoder.js +453 -0
  62. package/dist/messages/message-encoder.js.map +1 -0
  63. package/dist/messages/send.d.ts +101 -0
  64. package/dist/messages/send.d.ts.map +1 -0
  65. package/dist/messages/send.js +409 -0
  66. package/dist/messages/send.js.map +1 -0
  67. package/dist/recovery/index.d.ts +125 -0
  68. package/dist/recovery/index.d.ts.map +1 -0
  69. package/dist/recovery/index.js +584 -0
  70. package/dist/recovery/index.js.map +1 -0
  71. package/dist/skdm/index.d.ts +220 -0
  72. package/dist/skdm/index.d.ts.map +1 -0
  73. package/dist/skdm/index.js +600 -0
  74. package/dist/skdm/index.js.map +1 -0
  75. package/dist/socket/hybrid.d.ts +118 -0
  76. package/dist/socket/hybrid.d.ts.map +1 -0
  77. package/dist/socket/hybrid.js +352 -0
  78. package/dist/socket/hybrid.js.map +1 -0
  79. package/dist/socket/wa-socket.d.ts +300 -0
  80. package/dist/socket/wa-socket.d.ts.map +1 -0
  81. package/dist/socket/wa-socket.js +1094 -0
  82. package/dist/socket/wa-socket.js.map +1 -0
  83. package/dist/socket/ws-socket.d.ts +96 -0
  84. package/dist/socket/ws-socket.d.ts.map +1 -0
  85. package/dist/socket/ws-socket.js +302 -0
  86. package/dist/socket/ws-socket.js.map +1 -0
  87. package/dist/types/index.d.ts +444 -0
  88. package/dist/types/index.d.ts.map +1 -0
  89. package/dist/types/index.js +7 -0
  90. package/dist/types/index.js.map +1 -0
  91. package/dist/utils/jid.d.ts +46 -0
  92. package/dist/utils/jid.d.ts.map +1 -0
  93. package/dist/utils/jid.js +152 -0
  94. package/dist/utils/jid.js.map +1 -0
  95. package/dist/utils/logger.d.ts +45 -0
  96. package/dist/utils/logger.d.ts.map +1 -0
  97. package/dist/utils/logger.js +79 -0
  98. package/dist/utils/logger.js.map +1 -0
  99. package/dist/utils/retry.d.ts +43 -0
  100. package/dist/utils/retry.d.ts.map +1 -0
  101. package/dist/utils/retry.js +175 -0
  102. package/dist/utils/retry.js.map +1 -0
  103. package/docs/API.md +745 -0
  104. package/docs/ARCHITECTURE.md +307 -0
  105. package/docs/BAILEYS_FIXES.md +360 -0
  106. package/docs/FEATURES.md +532 -0
  107. package/docs/PROTOCOL.md +489 -0
  108. package/docs/RECOVERY.md +409 -0
  109. package/docs/SKDM.md +233 -0
  110. package/examples/ai-bot/index.ts +479 -0
  111. package/examples/ai-bot/package.json +17 -0
  112. package/examples/echo-bot/index.ts +171 -0
  113. package/examples/echo-bot/package.json +18 -0
  114. package/examples/group-bot/index.ts +557 -0
  115. package/examples/group-bot/package.json +17 -0
  116. package/examples/rest-gateway/index.ts +499 -0
  117. package/examples/rest-gateway/package.json +19 -0
  118. package/package.json +75 -0
  119. package/src/advanced/features.ts +817 -0
  120. package/src/auth/auth-state.ts +342 -0
  121. package/src/auth/pairing-code.ts +246 -0
  122. package/src/auth/qr-code.ts +191 -0
  123. package/src/calls/handler.ts +153 -0
  124. package/src/core/binary.ts +464 -0
  125. package/src/core/crypto.ts +189 -0
  126. package/src/core/noise.ts +406 -0
  127. package/src/events/emitter.ts +88 -0
  128. package/src/features/index.ts +921 -0
  129. package/src/fixes/index.ts +882 -0
  130. package/src/groups/management.ts +497 -0
  131. package/src/index.ts +274 -0
  132. package/src/messages/media.ts +372 -0
  133. package/src/messages/message-encoder.ts +520 -0
  134. package/src/messages/send.ts +521 -0
  135. package/src/recovery/index.ts +704 -0
  136. package/src/skdm/index.ts +693 -0
  137. package/src/socket/hybrid.ts +414 -0
  138. package/src/socket/wa-socket.ts +1347 -0
  139. package/src/socket/ws-socket.ts +355 -0
  140. package/src/types/index.ts +457 -0
  141. package/src/utils/jid.ts +156 -0
  142. package/src/utils/logger.ts +84 -0
  143. package/src/utils/retry.ts +205 -0
  144. package/tsconfig.json +30 -0
@@ -0,0 +1,479 @@
1
+ /**
2
+ * AI Bot Example - WhatsApp bot dengan GPT integration
3
+ *
4
+ * Features:
5
+ * - Conversational AI dengan OpenAI GPT
6
+ * - Persistent conversation context per user
7
+ * - Image recognition (kirim image, bot describe)
8
+ * - Voice message transcription
9
+ * - Multi-language support
10
+ * - Rate limiting per user (RAM efficient)
11
+ *
12
+ * Setup:
13
+ * 1. npm install openai
14
+ * 2. Set OPENAI_API_KEY environment variable
15
+ * 3. npm start
16
+ *
17
+ * Usage:
18
+ * - Kirim pesan text apapun, bot akan reply dengan AI-generated response
19
+ * - !reset untuk reset conversation context
20
+ * - !voice untuk transcribe voice message terakhir
21
+ * - !image untuk generate image (DALL-E)
22
+ */
23
+
24
+ import {
25
+ makeWASocket,
26
+ useFileAuthState,
27
+ type WAMessage,
28
+ type JIDLike,
29
+ jidToString,
30
+ extractMessageText,
31
+ hasMedia
32
+ } from 'waliwa';
33
+
34
+ // ============== Config ==============
35
+
36
+ const CONFIG = {
37
+ model: process.env.OPENAI_MODEL ?? 'gpt-4o-mini',
38
+ maxTokens: parseInt(process.env.MAX_TOKENS ?? '500', 10),
39
+ temperature: parseFloat(process.env.TEMPERATURE ?? '0.7'),
40
+ systemPrompt: process.env.SYSTEM_PROMPT ?? `You are a helpful WhatsApp assistant.
41
+ - Reply in same language as user
42
+ - Keep responses concise (max 3 paragraphs)
43
+ - Be friendly and helpful
44
+ - If user asks about your identity, say you're an AI assistant on WhatsApp powered by Waliwa`,
45
+ maxHistory: 10, // Max messages per conversation
46
+ rateLimitPerMinute: 20, // Per user
47
+ conversationTimeoutMs: 30 * 60 * 1000, // 30 mins idle = reset context
48
+ };
49
+
50
+ // ============== Conversation Manager (RAM efficient) ==============
51
+
52
+ interface ConversationMessage {
53
+ role: 'user' | 'assistant' | 'system';
54
+ content: string;
55
+ timestamp: number;
56
+ }
57
+
58
+ class ConversationManager {
59
+ private conversations = new Map<string, ConversationMessage[]>();
60
+ private lastActivity = new Map<string, number>();
61
+ private maxHistory: number;
62
+ private timeoutMs: number;
63
+
64
+ constructor(maxHistory: number = 10, timeoutMs: number = 30 * 60 * 1000) {
65
+ this.maxHistory = maxHistory;
66
+ this.timeoutMs = timeoutMs;
67
+ }
68
+
69
+ getUserKey(jid: JIDLike): string {
70
+ return jidToString(jid).split(':')[0]; // ignore device suffix
71
+ }
72
+
73
+ addMessage(jid: JIDLike, message: ConversationMessage): void {
74
+ const key = this.getUserKey(jid);
75
+ let conv = this.conversations.get(key);
76
+ if (!conv) {
77
+ conv = [];
78
+ this.conversations.set(key, conv);
79
+ }
80
+ conv.push(message);
81
+ // Trim to maxHistory (keep system + last N messages)
82
+ if (conv.length > this.maxHistory) {
83
+ // Keep system prompt (if first) + recent messages
84
+ const systemMsgs = conv.filter(m => m.role === 'system');
85
+ const otherMsgs = conv.filter(m => m.role !== 'system');
86
+ const trimmed = [...systemMsgs.slice(-1), ...otherMsgs.slice(-this.maxHistory + 1)];
87
+ this.conversations.set(key, trimmed);
88
+ }
89
+ this.lastActivity.set(key, Date.now());
90
+ }
91
+
92
+ getHistory(jid: JIDLike): ConversationMessage[] {
93
+ const key = this.getUserKey(jid);
94
+ this.cleanup(key);
95
+ return this.conversations.get(key) ?? [];
96
+ }
97
+
98
+ reset(jid: JIDLike): void {
99
+ const key = this.getUserKey(jid);
100
+ this.conversations.delete(key);
101
+ this.lastActivity.delete(key);
102
+ }
103
+
104
+ private cleanup(key: string): void {
105
+ const last = this.lastActivity.get(key);
106
+ if (last && Date.now() - last > this.timeoutMs) {
107
+ this.conversations.delete(key);
108
+ this.lastActivity.delete(key);
109
+ }
110
+ }
111
+
112
+ /** Cleanup all expired conversations - call periodically untuk hemat RAM */
113
+ cleanupAll(): void {
114
+ const now = Date.now();
115
+ for (const [key, last] of this.lastActivity.entries()) {
116
+ if (now - last > this.timeoutMs) {
117
+ this.conversations.delete(key);
118
+ this.lastActivity.delete(key);
119
+ }
120
+ }
121
+ }
122
+
123
+ size(): number {
124
+ return this.conversations.size;
125
+ }
126
+ }
127
+
128
+ // ============== Rate Limiter (per user) ==============
129
+
130
+ class UserRateLimiter {
131
+ private counts = new Map<string, { count: number; resetAt: number }>();
132
+ private maxPerMinute: number;
133
+
134
+ constructor(maxPerMinute: number = 20) {
135
+ this.maxPerMinute = maxPerMinute;
136
+ }
137
+
138
+ canProceed(userKey: string): boolean {
139
+ const now = Date.now();
140
+ let entry = this.counts.get(userKey);
141
+ if (!entry || now > entry.resetAt) {
142
+ entry = { count: 0, resetAt: now + 60000 };
143
+ this.counts.set(userKey, entry);
144
+ }
145
+ if (entry.count >= this.maxPerMinute) return false;
146
+ entry.count++;
147
+ return true;
148
+ }
149
+
150
+ cleanup(): void {
151
+ const now = Date.now();
152
+ for (const [key, entry] of this.counts.entries()) {
153
+ if (now > entry.resetAt) {
154
+ this.counts.delete(key);
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ // ============== OpenAI Client (lazy loaded) ==============
161
+
162
+ let openaiClient: any = null;
163
+
164
+ async function getOpenAIClient(): Promise<any> {
165
+ if (openaiClient) return openaiClient;
166
+ const apiKey = process.env.OPENAI_API_KEY;
167
+ if (!apiKey) {
168
+ throw new Error('OPENAI_API_KEY environment variable not set');
169
+ }
170
+ // Dynamic import - avoids dependency jika gak dipakai
171
+ const { default: OpenAI } = await import('openai');
172
+ openaiClient = new OpenAI({ apiKey });
173
+ return openaiClient;
174
+ }
175
+
176
+ // ============== Main ==============
177
+
178
+ const conversations = new ConversationManager(CONFIG.maxHistory, CONFIG.conversationTimeoutMs);
179
+ const rateLimiter = new UserRateLimiter(CONFIG.rateLimitPerMinute);
180
+
181
+ async function main() {
182
+ console.log('Starting AI bot...');
183
+
184
+ if (!process.env.OPENAI_API_KEY) {
185
+ console.warn('Warning: OPENAI_API_KEY not set. Bot will run but AI responses will fail.');
186
+ }
187
+
188
+ const { state } = await useFileAuthState('./auth');
189
+
190
+ const sock = makeWASocket({
191
+ authState: state,
192
+ printQRInTerminal: true,
193
+ ramOptimizationLevel: 3, // Aggressive untuk hemat RAM karena ada OpenAI context
194
+ maxReconnectAttempts: 10
195
+ });
196
+
197
+ sock.ev.on('connection.update', (update) => {
198
+ const { connection, qr } = update;
199
+ if (qr) console.log('Scan QR dengan WhatsApp mobile...');
200
+ if (connection === 'open') {
201
+ console.log('✓ AI bot online sebagai', sock.user?.id);
202
+ console.log(`Using model: ${CONFIG.model}`);
203
+ }
204
+ if (connection === 'close') console.log('Connection closed, reconnecting...');
205
+ });
206
+
207
+ sock.ev.on('message.upsert', async ({ messages, type }) => {
208
+ if (type !== 'notify') return;
209
+ for (const message of messages) {
210
+ try {
211
+ await handleMessage(sock, message);
212
+ } catch (err: any) {
213
+ console.error('Error:', err.message);
214
+ }
215
+ }
216
+ });
217
+
218
+ // Cleanup expired conversations tiap 5 menit (hemat RAM)
219
+ setInterval(() => {
220
+ conversations.cleanupAll();
221
+ rateLimiter.cleanup();
222
+ console.log(`Active conversations: ${conversations.size()}`);
223
+ }, 5 * 60 * 1000);
224
+
225
+ console.log('AI bot ready. Send a message to start chatting!');
226
+ }
227
+
228
+ // ============== Message Handler ==============
229
+
230
+ async function handleMessage(sock: ReturnType<typeof makeWASocket>, message: WAMessage) {
231
+ if (message.key.fromMe) return;
232
+
233
+ const from = message.key.remoteJid;
234
+ const userKey = jidToString(from).split(':')[0];
235
+ const text = extractMessageText(message.message) || '';
236
+ const pushName = message.pushName || 'User';
237
+
238
+ if (!text) {
239
+ // Check for media (image for vision)
240
+ if (hasMedia(message.message)) {
241
+ await handleMedia(sock, from, message);
242
+ }
243
+ return;
244
+ }
245
+
246
+ console.log(`[${from}] ${pushName}: ${text.substring(0, 50)}${text.length > 50 ? '...' : ''}`);
247
+
248
+ // Auto read receipt
249
+ await sock.sendReadReceipt(from, [message.key.id]).catch(() => {});
250
+
251
+ // Send typing indicator
252
+ await sock.sendPresenceUpdate(from, 'composing').catch(() => {});
253
+
254
+ // Rate limit check
255
+ if (!rateLimiter.canProceed(userKey)) {
256
+ await sock.sendMessage(from, {
257
+ text: '⚠️ Kamu terlalu banyak nanya. Tunggu sebentar ya (rate limit).'
258
+ });
259
+ return;
260
+ }
261
+
262
+ // Commands
263
+ if (text.startsWith('!')) {
264
+ await handleCommand(sock, from, text, message);
265
+ return;
266
+ }
267
+
268
+ // Generate AI response
269
+ try {
270
+ const response = await generateAIResponse(from, text, pushName);
271
+ await sock.sendMessage(from, { text: response });
272
+ } catch (err: any) {
273
+ console.error('AI response error:', err.message);
274
+ await sock.sendMessage(from, {
275
+ text: 'Maaf, ada error. Coba lagi nanti ya. Ketik !reset untuk reset conversation.'
276
+ });
277
+ }
278
+ }
279
+
280
+ // ============== AI Generation ==============
281
+
282
+ async function generateAIResponse(
283
+ from: JIDLike,
284
+ userText: string,
285
+ userName: string
286
+ ): Promise<string> {
287
+ const openai = await getOpenAIClient();
288
+
289
+ // Add user message ke history
290
+ conversations.addMessage(from, {
291
+ role: 'user',
292
+ content: userText,
293
+ timestamp: Date.now()
294
+ });
295
+
296
+ // Build messages array dengan system prompt
297
+ const systemMessage: ConversationMessage = {
298
+ role: 'system',
299
+ content: `${CONFIG.systemPrompt}\n\nUser name: ${userName}`,
300
+ timestamp: Date.now()
301
+ };
302
+
303
+ const history = conversations.getHistory(from);
304
+ const messages = [
305
+ systemMessage,
306
+ ...history.map(m => ({ role: m.role, content: m.content }))
307
+ ];
308
+
309
+ // Call OpenAI
310
+ const response = await openai.chat.completions.create({
311
+ model: CONFIG.model,
312
+ messages,
313
+ max_tokens: CONFIG.maxTokens,
314
+ temperature: CONFIG.temperature
315
+ });
316
+
317
+ const reply = response.choices[0]?.message?.content ?? 'Maaf, saya tidak paham.';
318
+
319
+ // Add assistant reply ke history
320
+ conversations.addMessage(from, {
321
+ role: 'assistant',
322
+ content: reply,
323
+ timestamp: Date.now()
324
+ });
325
+
326
+ return reply;
327
+ }
328
+
329
+ // ============== Media Handler ==============
330
+
331
+ async function handleMedia(
332
+ sock: ReturnType<typeof makeWASocket>,
333
+ from: JIDLike,
334
+ message: WAMessage
335
+ ) {
336
+ try {
337
+ // Check if image
338
+ if (message.message?.image) {
339
+ await sock.sendMessage(from, { text: '👀 Melihat gambar...' });
340
+
341
+ // Download image
342
+ const mediaInfo = message.message.image;
343
+ if (!mediaInfo.stream && !mediaInfo.url) {
344
+ await sock.sendMessage(from, { text: '❌ Tidak bisa download gambar' });
345
+ return;
346
+ }
347
+
348
+ // Download dan convert ke base64
349
+ // Note: actual download requires mediaKey from message - simplified here
350
+ // const buffer = await sock.downloadMedia(url, mediaKey, 'image', 'image/jpeg');
351
+ // const base64 = buffer.toString('base64');
352
+
353
+ // For now, just acknowledge
354
+ await sock.sendMessage(from, {
355
+ text: '✓ Gambar diterima. Image analysis akan diimplementasikan.'
356
+ });
357
+ } else if (message.message?.audio) {
358
+ await sock.sendMessage(from, { text: '🎧 Voice message diterima. Ketik !transcribe untuk transcribe.' });
359
+ }
360
+ } catch (err: any) {
361
+ console.error('Media handler error:', err.message);
362
+ }
363
+ }
364
+
365
+ // ============== Commands ==============
366
+
367
+ async function handleCommand(
368
+ sock: ReturnType<typeof makeWASocket>,
369
+ from: JIDLike,
370
+ text: string,
371
+ message: WAMessage
372
+ ) {
373
+ const [cmd, ...args] = text.slice(1).split(' ');
374
+ const cmdLower = cmd.toLowerCase();
375
+
376
+ switch (cmdLower) {
377
+ case 'reset':
378
+ conversations.reset(from);
379
+ await sock.sendMessage(from, { text: '✓ Conversation context direset' });
380
+ break;
381
+
382
+ case 'help':
383
+ await sock.sendMessage(from, {
384
+ text: [
385
+ '*AI Bot Commands*',
386
+ '',
387
+ '!reset - Reset conversation context',
388
+ '!help - Tampilkan bantuan ini',
389
+ '!image <prompt> - Generate image dengan DALL-E',
390
+ '!transcribe - Transcribe voice message terakhir',
391
+ '!info - Info bot & statistik',
392
+ '',
393
+ 'Atau kirim pesan apapun untuk chat dengan AI!'
394
+ ].join('\n')
395
+ });
396
+ break;
397
+
398
+ case 'info':
399
+ await sock.sendMessage(from, {
400
+ text: [
401
+ '*Bot Info*',
402
+ `Model: ${CONFIG.model}`,
403
+ `Max tokens: ${CONFIG.maxTokens}`,
404
+ `Temperature: ${CONFIG.temperature}`,
405
+ `Active conversations: ${conversations.size()}`,
406
+ `Rate limit: ${CONFIG.rateLimitPerMinute}/min per user`
407
+ ].join('\n')
408
+ });
409
+ break;
410
+
411
+ case 'image':
412
+ const prompt = args.join(' ');
413
+ if (!prompt) {
414
+ await sock.sendMessage(from, { text: 'Usage: !image <prompt>' });
415
+ return;
416
+ }
417
+ await generateAndSendImage(sock, from, prompt);
418
+ break;
419
+
420
+ case 'transcribe':
421
+ await sock.sendMessage(from, {
422
+ text: 'Voice transcription akan diimplementasikan. Untuk sekarang, ketik ulang pesannya ya.'
423
+ });
424
+ break;
425
+
426
+ default:
427
+ // Unknown command - treat as regular message
428
+ break;
429
+ }
430
+ }
431
+
432
+ async function generateAndSendImage(
433
+ sock: ReturnType<typeof makeWASocket>,
434
+ from: JIDLike,
435
+ prompt: string
436
+ ) {
437
+ try {
438
+ const openai = await getOpenAIClient();
439
+ await sock.sendMessage(from, { text: `🎨 Generating image: "${prompt}"...` });
440
+
441
+ const response = await openai.images.generate({
442
+ model: 'dall-e-3',
443
+ prompt,
444
+ n: 1,
445
+ size: '1024x1024',
446
+ response_format: 'b64_json'
447
+ });
448
+
449
+ const base64 = response.data[0]?.b64_json;
450
+ if (!base64) {
451
+ await sock.sendMessage(from, { text: '❌ Gagal generate image' });
452
+ return;
453
+ }
454
+
455
+ const buffer = Buffer.from(base64, 'base64');
456
+
457
+ // Upload ke WhatsApp dan kirim
458
+ // Note: Actual upload via sock.uploadMedia - simplified here
459
+ await sock.sendMessage(from, {
460
+ image: { stream: buffer as any, mimetype: 'image/png' },
461
+ caption: `✨ Generated: "${prompt}"`
462
+ }).catch(async (err: any) => {
463
+ console.error('Image send failed:', err.message);
464
+ await sock.sendMessage(from, {
465
+ text: `✓ Image generated (but failed to send). URL: ${response.data[0]?.url ?? 'N/A'}`
466
+ });
467
+ });
468
+ } catch (err: any) {
469
+ console.error('Image gen error:', err.message);
470
+ await sock.sendMessage(from, { text: `❌ Error: ${err.message}` });
471
+ }
472
+ }
473
+
474
+ // ============== Run ==============
475
+
476
+ main().catch(err => {
477
+ console.error('Fatal error:', err);
478
+ process.exit(1);
479
+ });
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "waliwa-ai-bot",
3
+ "version": "1.0.0",
4
+ "description": "WhatsApp bot dengan GPT integration untuk conversational AI",
5
+ "main": "index.ts",
6
+ "scripts": {
7
+ "start": "ts-node index.ts"
8
+ },
9
+ "dependencies": {
10
+ "waliwa": "file:../../"
11
+ },
12
+ "devDependencies": {
13
+ "@types/node": "^20.11.0",
14
+ "typescript": "^5.3.3",
15
+ "ts-node": "^10.9.2"
16
+ }
17
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Echo Bot Example - Basic WhatsApp bot menggunakan Waliwa
3
+ *
4
+ * Features:
5
+ * - Auto scan QR (atau pakai existing session)
6
+ * - Reply dengan echo untuk setiap pesan masuk
7
+ * - Command: !ping, !echo <text>, !help
8
+ *
9
+ * Usage:
10
+ * npm install
11
+ * npm start
12
+ *
13
+ * Scan QR yang muncul di terminal dengan WhatsApp mobile app.
14
+ */
15
+
16
+ import { makeWASocket, useFileAuthState, type WAMessage } from 'waliwa';
17
+
18
+ async function main() {
19
+ // Initialize auth state - simpan ke folder ./auth
20
+ const { state } = await useFileAuthState('./auth');
21
+
22
+ // Create socket dengan QR print ke terminal
23
+ const sock = makeWASocket({
24
+ authState: state,
25
+ printQRInTerminal: true,
26
+ // RAM optimization level: 2 = balanced, 3 = aggressive (untuk low-end devices)
27
+ ramOptimizationLevel: 2,
28
+ // Auto reconnect on disconnect
29
+ maxReconnectAttempts: 10,
30
+ reconnectInterval: 2000
31
+ });
32
+
33
+ // Connection status
34
+ sock.ev.on('connection.update', (update) => {
35
+ const { connection, lastDisconnect, qr } = update;
36
+
37
+ if (qr) {
38
+ console.log('\n=== QR Code tersedia - scan dengan WhatsApp mobile ===\n');
39
+ }
40
+
41
+ if (connection === 'connecting') {
42
+ console.log('Connecting ke WhatsApp server...');
43
+ }
44
+
45
+ if (connection === 'open') {
46
+ console.log('\n=== Bot terhubung! ===');
47
+ console.log(`Logged in sebagai: ${sock.user?.id ?? 'unknown'}\n`);
48
+ }
49
+
50
+ if (connection === 'close') {
51
+ const reason = lastDisconnect?.output;
52
+ console.log('Connection closed:', reason);
53
+ // Reconnect jika bukan intentional
54
+ if (reason?.code !== 1000) {
55
+ console.log('Akan reconnect otomatis...');
56
+ }
57
+ }
58
+ });
59
+
60
+ // Handle incoming messages
61
+ sock.ev.on('message.upsert', async ({ messages, type }) => {
62
+ // Hanya proses pesan baru (bukan history sync)
63
+ if (type !== 'notify') return;
64
+
65
+ for (const message of messages) {
66
+ await handleMessage(sock, message);
67
+ }
68
+ });
69
+
70
+ // Handle message status updates (read receipts)
71
+ sock.ev.on('message.update', ({ messages }) => {
72
+ for (const msg of messages) {
73
+ if (msg.status === 'read') {
74
+ console.log(`Message ${msg.key.id} was read`);
75
+ }
76
+ }
77
+ });
78
+
79
+ console.log('Echo bot started. Waiting for messages...');
80
+ }
81
+
82
+ async function handleMessage(sock: ReturnType<typeof makeWASocket>, message: WAMessage) {
83
+ try {
84
+ // Skip jika pesan dari diri sendiri
85
+ if (message.key.fromMe) return;
86
+
87
+ const from = message.key.remoteJid;
88
+ const text = message.message?.text || '';
89
+ const pushName = message.pushName || 'Unknown';
90
+
91
+ // Skip jika tidak ada text
92
+ if (!text) return;
93
+
94
+ console.log(`[${from}] ${pushName}: ${text}`);
95
+
96
+ // Auto read receipt
97
+ await sock.sendReadReceipt(from, [message.key.id]).catch(() => {});
98
+
99
+ // Parse command
100
+ const [cmd, ...args] = text.split(' ');
101
+ const cmdLower = cmd.toLowerCase();
102
+
103
+ switch (cmdLower) {
104
+ case '!ping':
105
+ await sock.sendMessage(from, { text: 'pong! 🏓' });
106
+ break;
107
+
108
+ case '!echo':
109
+ if (args.length === 0) {
110
+ await sock.sendMessage(from, { text: 'Usage: !echo <text>' });
111
+ } else {
112
+ await sock.sendMessage(from, { text: args.join(' ') });
113
+ }
114
+ break;
115
+
116
+ case '!help':
117
+ await sock.sendMessage(from, {
118
+ text: [
119
+ '*Echo Bot Commands*',
120
+ '',
121
+ '!ping - Check if bot alive',
122
+ '!echo <text> - Reply dengan text yang sama',
123
+ '!help - Tampilkan bantuan ini',
124
+ '!info - Info bot',
125
+ '!time - Jam server',
126
+ ''
127
+ ].join('\n')
128
+ });
129
+ break;
130
+
131
+ case '!info':
132
+ await sock.sendMessage(from, {
133
+ text: [
134
+ '*Bot Info*',
135
+ `Library: Waliwa v1.0.0`,
136
+ `Protocol: WhatsApp Web MD`,
137
+ `Status: Online`,
138
+ `RAM Mode: Balanced`
139
+ ].join('\n')
140
+ });
141
+ break;
142
+
143
+ case '!time':
144
+ const now = new Date();
145
+ await sock.sendMessage(from, {
146
+ text: `Server time: ${now.toLocaleString('id-ID')}`
147
+ });
148
+ break;
149
+
150
+ default:
151
+ // Echo semua pesan lain
152
+ await sock.sendMessage(from, {
153
+ text: `Echo: ${text}`,
154
+ quoted: message
155
+ });
156
+ }
157
+ } catch (err: any) {
158
+ console.error('Error handling message:', err.message);
159
+ }
160
+ }
161
+
162
+ // Graceful shutdown
163
+ process.on('SIGINT', async () => {
164
+ console.log('\nShutting down...');
165
+ process.exit(0);
166
+ });
167
+
168
+ main().catch(err => {
169
+ console.error('Fatal error:', err);
170
+ process.exit(1);
171
+ });
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "waliwa-echo-bot",
3
+ "version": "1.0.0",
4
+ "description": "Basic echo bot menggunakan Waliwa library",
5
+ "main": "index.ts",
6
+ "scripts": {
7
+ "start": "ts-node index.ts",
8
+ "build": "tsc"
9
+ },
10
+ "dependencies": {
11
+ "waliwa": "file:../../"
12
+ },
13
+ "devDependencies": {
14
+ "@types/node": "^20.11.0",
15
+ "typescript": "^5.3.3",
16
+ "ts-node": "^10.9.2"
17
+ }
18
+ }