@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,557 @@
1
+ /**
2
+ * Group Bot Example - Bot manajemen grup WhatsApp dengan admin commands
3
+ *
4
+ * Features:
5
+ * - Welcome message untuk member baru
6
+ * - Goodbye message saat member keluar
7
+ * - Anti-spam dengan rate limiting
8
+ * - Anti-link (auto-hapus link di grup)
9
+ * - Admin commands: !kick, !promote, !demote, !mute, !unmute
10
+ * - Tagall, hidetag untuk mention semua member
11
+ * - Group info, !setsubject, !setdesc
12
+ */
13
+
14
+ import {
15
+ makeWASocket,
16
+ useFileAuthState,
17
+ isGroupJid,
18
+ jidToString,
19
+ type WAMessage,
20
+ type WAGroupMetadata,
21
+ type JIDLike
22
+ } from 'waliwa';
23
+
24
+ // ============== Config ==============
25
+
26
+ const CONFIG = {
27
+ prefix: '!',
28
+ ownerNumber: process.env.OWNER_NUMBER ?? '6281234567890', // Ganti dengan nomor lo
29
+ welcomeMessage: 'Selamat datang di grup {groupName}, {userName}! 🎉',
30
+ goodbyeMessage: 'Sampai jumpa {userName}! 👋',
31
+ antiLinkEnabled: true,
32
+ antiSpamEnabled: true,
33
+ spamThreshold: 5, // max 5 messages per 10 seconds
34
+ spamWindowMs: 10000,
35
+ muteDurationMs: 60 * 60 * 1000, // 1 jam default mute
36
+ tagAllCooldownMs: 30000 // 30 detik cooldown
37
+ };
38
+
39
+ // ============== State ==============
40
+
41
+ interface UserSpamTracker {
42
+ messageCount: number;
43
+ lastReset: number;
44
+ warnings: number;
45
+ }
46
+
47
+ const spamTracker = new Map<string, UserSpamTracker>();
48
+ const mutedUsers = new Set<string>(); // groupJid:userJid
49
+ const lastTagAll = new Map<string, number>(); // groupJid -> timestamp
50
+
51
+ // ============== Main ==============
52
+
53
+ async function main() {
54
+ const { state } = await useFileAuthState('./auth');
55
+
56
+ const sock = makeWASocket({
57
+ authState: state,
58
+ printQRInTerminal: true,
59
+ ramOptimizationLevel: 2,
60
+ maxReconnectAttempts: 10
61
+ });
62
+
63
+ sock.ev.on('connection.update', (update) => {
64
+ const { connection, qr } = update;
65
+ if (qr) console.log('Scan QR dengan WhatsApp mobile...');
66
+ if (connection === 'open') console.log('✓ Group bot online sebagai', sock.user?.id);
67
+ if (connection === 'close') console.log('Connection closed, akan reconnect...');
68
+ });
69
+
70
+ // Handle incoming messages
71
+ sock.ev.on('message.upsert', async ({ messages, type }) => {
72
+ if (type !== 'notify') return;
73
+ for (const message of messages) {
74
+ try {
75
+ await handleMessage(sock, message);
76
+ } catch (err: any) {
77
+ console.error('Error handling message:', err.message);
78
+ }
79
+ }
80
+ });
81
+
82
+ // Handle group participant updates (join/leave/promote/demote)
83
+ sock.ev.on('group-participants.update', async (update) => {
84
+ const { id: groupJid, participants, action, author } = update;
85
+ try {
86
+ const metadata = await sock.groupMetadata(groupJid);
87
+ const groupName = metadata.subject;
88
+
89
+ for (const participant of participants) {
90
+ const userName = await getUserName(sock, participant);
91
+
92
+ if (action === 'add') {
93
+ console.log(`${userName} joined group ${groupName}`);
94
+ const welcomeMsg = CONFIG.welcomeMessage
95
+ .replace('{groupName}', groupName)
96
+ .replace('{userName}', userName);
97
+ await sock.sendMessage(groupJid, {
98
+ text: welcomeMsg,
99
+ mentions: [participant]
100
+ });
101
+ } else if (action === 'remove') {
102
+ console.log(`${userName} left group ${groupName}`);
103
+ const goodbyeMsg = CONFIG.goodbyeMessage
104
+ .replace('{groupName}', groupName)
105
+ .replace('{userName}', userName);
106
+ await sock.sendMessage(groupJid, { text: goodbyeMsg });
107
+ } else if (action === 'promote') {
108
+ await sock.sendMessage(groupJid, {
109
+ text: `${userName} telah dipromosikan menjadi admin 👑`,
110
+ mentions: [participant]
111
+ });
112
+ } else if (action === 'demote') {
113
+ await sock.sendMessage(groupJid, {
114
+ text: `${userName} telah di-demote dari admin`,
115
+ mentions: [participant]
116
+ });
117
+ }
118
+ }
119
+ } catch (err: any) {
120
+ console.error('Error in group update:', err.message);
121
+ }
122
+ });
123
+
124
+ console.log('Group bot started. Waiting for messages...');
125
+ }
126
+
127
+ // ============== Message Handler ==============
128
+
129
+ async function handleMessage(sock: ReturnType<typeof makeWASocket>, message: WAMessage) {
130
+ if (message.key.fromMe) return;
131
+
132
+ const from = message.key.remoteJid;
133
+ const text = message.message?.text || '';
134
+ const sender = message.key.participant ?? message.key.remoteJid;
135
+ const pushName = message.pushName || 'User';
136
+
137
+ if (!text) return;
138
+
139
+ // Cek apakah di grup
140
+ const isGroup = isGroupJid(from);
141
+ if (!isGroup) {
142
+ // DM mode - reply sederhana
143
+ if (text === '!menu') {
144
+ await sock.sendMessage(from, {
145
+ text: 'Bot ini bekerja di grup. Add bot ke grup untuk fitur lengkap!'
146
+ });
147
+ }
148
+ return;
149
+ }
150
+
151
+ // Check mute status
152
+ const muteKey = `${jidToString(from)}:${jidToString(sender)}`;
153
+ if (mutedUsers.has(muteKey) && text.startsWith(CONFIG.prefix)) {
154
+ return; // Ignore commands dari muted users
155
+ }
156
+
157
+ // Anti-spam check
158
+ if (CONFIG.antiSpamEnabled) {
159
+ if (isSpamming(jidToString(sender))) {
160
+ await warnAndMute(sock, from, sender, pushName);
161
+ return;
162
+ }
163
+ }
164
+
165
+ // Anti-link check
166
+ if (CONFIG.antiLinkEnabled && containsLink(text)) {
167
+ if (!text.startsWith(CONFIG.prefix)) {
168
+ await handleAntiLink(sock, from, sender, message);
169
+ return;
170
+ }
171
+ }
172
+
173
+ // Parse command
174
+ if (!text.startsWith(CONFIG.prefix)) return;
175
+ const [cmd, ...args] = text.slice(1).split(' ');
176
+ const cmdLower = cmd.toLowerCase();
177
+
178
+ console.log(`[${from}] ${pushName}: ${text}`);
179
+
180
+ // Get group metadata
181
+ let metadata: WAGroupMetadata | undefined;
182
+ try {
183
+ metadata = await sock.groupMetadata(from);
184
+ } catch {
185
+ // ignore
186
+ }
187
+
188
+ const isAdmin = metadata?.participants.find(p => jidToString(p.id) === jidToString(sender))?.isAdmin ?? false;
189
+ const isOwner = jidUser(jidToString(sender)) === CONFIG.ownerNumber;
190
+
191
+ switch (cmdLower) {
192
+ case 'menu':
193
+ case 'help':
194
+ await sendMenu(sock, from);
195
+ break;
196
+
197
+ case 'groupinfo':
198
+ if (metadata) {
199
+ await sock.sendMessage(from, {
200
+ text: [
201
+ `*Group Info*`,
202
+ ``,
203
+ `Nama: ${metadata.subject}`,
204
+ `Members: ${metadata.participants.length}`,
205
+ `Admins: ${metadata.participants.filter(p => p.isAdmin).length}`,
206
+ `Created: ${metadata.creation ? new Date(metadata.creation * 1000).toLocaleDateString('id-ID') : 'unknown'}`,
207
+ `Desc: ${metadata.desc ?? 'No description'}`
208
+ ].join('\n')
209
+ });
210
+ }
211
+ break;
212
+
213
+ case 'kick':
214
+ if (!isAdmin && !isOwner) {
215
+ await sock.sendMessage(from, { text: '❌ Hanya admin yang bisa kick' });
216
+ return;
217
+ }
218
+ await handleKick(sock, from, sender, args, message);
219
+ break;
220
+
221
+ case 'promote':
222
+ if (!isAdmin && !isOwner) {
223
+ await sock.sendMessage(from, { text: '❌ Hanya admin yang bisa promote' });
224
+ return;
225
+ }
226
+ await handlePromote(sock, from, args, message, true);
227
+ break;
228
+
229
+ case 'demote':
230
+ if (!isAdmin && !isOwner) {
231
+ await sock.sendMessage(from, { text: '❌ Hanya admin yang bisa demote' });
232
+ return;
233
+ }
234
+ await handlePromote(sock, from, args, message, false);
235
+ break;
236
+
237
+ case 'tagall':
238
+ await handleTagAll(sock, from, metadata, sender);
239
+ break;
240
+
241
+ case 'hidetag':
242
+ if (!isAdmin && !isOwner) {
243
+ await sock.sendMessage(from, { text: '❌ Hanya admin yang bisa hidetag' });
244
+ return;
245
+ }
246
+ await handleHideTag(sock, from, args.join(' '), metadata);
247
+ break;
248
+
249
+ case 'setsubject':
250
+ if (!isAdmin && !isOwner) {
251
+ await sock.sendMessage(from, { text: '❌ Hanya admin yang bisa ubah nama grup' });
252
+ return;
253
+ }
254
+ if (args.length === 0) {
255
+ await sock.sendMessage(from, { text: 'Usage: !setsubject <new name>' });
256
+ return;
257
+ }
258
+ await sock.groupUpdateSubject(from, args.join(' '));
259
+ await sock.sendMessage(from, { text: '✓ Nama grup diubah' });
260
+ break;
261
+
262
+ case 'setdesc':
263
+ if (!isAdmin && !isOwner) {
264
+ await sock.sendMessage(from, { text: '❌ Hanya admin yang bisa ubah deskripsi' });
265
+ return;
266
+ }
267
+ if (args.length === 0) {
268
+ await sock.sendMessage(from, { text: 'Usage: !setdesc <new description>' });
269
+ return;
270
+ }
271
+ await sock.groupUpdateDescription(from, args.join(' '));
272
+ await sock.sendMessage(from, { text: '✓ Deskripsi grup diubah' });
273
+ break;
274
+
275
+ case 'mute':
276
+ if (!isAdmin && !isOwner) {
277
+ await sock.sendMessage(from, { text: '❌ Hanya admin yang bisa mute' });
278
+ return;
279
+ }
280
+ const target = await extractMentionedUser(sock, args, message);
281
+ if (target) {
282
+ mutedUsers.add(`${jidToString(from)}:${jidToString(target)}`);
283
+ await sock.sendMessage(from, {
284
+ text: `✓ ${pushName} telah di-mute selama 1 jam`,
285
+ mentions: [target]
286
+ });
287
+ setTimeout(() => {
288
+ mutedUsers.delete(`${jidToString(from)}:${jidToString(target)}`);
289
+ }, CONFIG.muteDurationMs);
290
+ }
291
+ break;
292
+
293
+ case 'unmute':
294
+ if (!isAdmin && !isOwner) {
295
+ await sock.sendMessage(from, { text: '❌ Hanya admin yang bisa unmute' });
296
+ return;
297
+ }
298
+ const unmuteTarget = await extractMentionedUser(sock, args, message);
299
+ if (unmuteTarget) {
300
+ mutedUsers.delete(`${jidToString(from)}:${jidToString(unmuteTarget)}`);
301
+ await sock.sendMessage(from, {
302
+ text: `✓ User telah di-unmute`,
303
+ mentions: [unmuteTarget]
304
+ });
305
+ }
306
+ break;
307
+
308
+ case 'invite':
309
+ const code = await sock.groupInviteCode(from);
310
+ await sock.sendMessage(from, {
311
+ text: `Link invite grup: https://chat.whatsapp.com/${code}`
312
+ });
313
+ break;
314
+
315
+ case 'revoke':
316
+ if (!isAdmin && !isOwner) {
317
+ await sock.sendMessage(from, { text: '❌ Hanya admin yang bisa revoke link' });
318
+ return;
319
+ }
320
+ await sock.groupRevokeInvite(from);
321
+ await sock.sendMessage(from, { text: '✓ Invite link di-revoke' });
322
+ break;
323
+
324
+ case 'poll':
325
+ await handlePoll(sock, from, args);
326
+ break;
327
+
328
+ default:
329
+ // Unknown command - ignore
330
+ break;
331
+ }
332
+ }
333
+
334
+ // ============== Helper Functions ==============
335
+
336
+ async function sendMenu(sock: ReturnType<typeof makeWASocket>, from: JIDLike) {
337
+ const menu = [
338
+ '*Group Bot Commands*',
339
+ '',
340
+ '*General:*',
341
+ '!menu - Tampilkan menu ini',
342
+ '!groupinfo - Info grup',
343
+ '!invite - Get invite link',
344
+ '',
345
+ '*Admin Commands:*',
346
+ '!kick @user - Kick member',
347
+ '!promote @user - Promote jadi admin',
348
+ '!demote @user - Demote admin',
349
+ '!setsubject <name> - Ubah nama grup',
350
+ '!setdesc <desc> - Ubah deskripsi',
351
+ '!revoke - Revoke invite link',
352
+ '!tagall - Mention semua member',
353
+ '!hidetag <msg> - Hidden mention',
354
+ '!mute @user - Mute user (1 jam)',
355
+ '!unmute @user - Unmute user',
356
+ '',
357
+ '*Fun:*',
358
+ '!poll <question> | opt1 | opt2 - Create poll'
359
+ ].join('\n');
360
+ await sock.sendMessage(from, { text: menu });
361
+ }
362
+
363
+ async function getUserName(sock: ReturnType<typeof makeWASocket>, jid: JIDLike): Promise<string> {
364
+ try {
365
+ // Try get from contacts - simplified
366
+ return jidToString(jid).split('@')[0];
367
+ } catch {
368
+ return 'Unknown';
369
+ }
370
+ }
371
+
372
+ function containsLink(text: string): boolean {
373
+ const linkPattern = /(https?:\/\/[^\s]+|www\.[^\s]+|[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(?:\/[^\s]*)?)/i;
374
+ return linkPattern.test(text);
375
+ }
376
+
377
+ async function handleAntiLink(
378
+ sock: ReturnType<typeof makeWASocket>,
379
+ from: JIDLike,
380
+ sender: JIDLike,
381
+ message: WAMessage
382
+ ) {
383
+ await sock.sendMessage(from, {
384
+ text: `⚠️ Link tidak diperbolehkan di grup ini!`,
385
+ quoted: message
386
+ });
387
+ // Delete message
388
+ await sock.deleteMessage(from, message.key, { revokeEveryone: true }).catch(() => {});
389
+ }
390
+
391
+ function isSpamming(userJid: string): boolean {
392
+ const now = Date.now();
393
+ let tracker = spamTracker.get(userJid);
394
+ if (!tracker) {
395
+ tracker = { messageCount: 0, lastReset: now, warnings: 0 };
396
+ spamTracker.set(userJid, tracker);
397
+ }
398
+ // Reset if window expired
399
+ if (now - tracker.lastReset > CONFIG.spamWindowMs) {
400
+ tracker.messageCount = 0;
401
+ tracker.lastReset = now;
402
+ }
403
+ tracker.messageCount++;
404
+ return tracker.messageCount > CONFIG.spamThreshold;
405
+ }
406
+
407
+ async function warnAndMute(
408
+ sock: ReturnType<typeof makeWASocket>,
409
+ from: JIDLike,
410
+ sender: JIDLike,
411
+ pushName: string
412
+ ) {
413
+ const tracker = spamTracker.get(jidToString(sender));
414
+ if (tracker) tracker.warnings++;
415
+
416
+ await sock.sendMessage(from, {
417
+ text: `⚠️ ${pushName} terdeteksi spam! Warning ${tracker?.warnings ?? 1}/${3}`,
418
+ mentions: [sender]
419
+ });
420
+
421
+ if ((tracker?.warnings ?? 0) >= 3) {
422
+ // Auto mute
423
+ mutedUsers.add(`${jidToString(from)}:${jidToString(sender)}`);
424
+ await sock.sendMessage(from, {
425
+ text: `🔇 ${pushName} telah di-auto-mute karena spam berlebihan`,
426
+ mentions: [sender]
427
+ });
428
+ setTimeout(() => {
429
+ mutedUsers.delete(`${jidToString(from)}:${jidToString(sender)}`);
430
+ }, CONFIG.muteDurationMs);
431
+ }
432
+ }
433
+
434
+ async function handleKick(
435
+ sock: ReturnType<typeof makeWASocket>,
436
+ from: JIDLike,
437
+ sender: JIDLike,
438
+ args: string[],
439
+ message: WAMessage
440
+ ) {
441
+ const target = await extractMentionedUser(sock, args, message);
442
+ if (!target) {
443
+ await sock.sendMessage(from, { text: 'Usage: !kick @user' });
444
+ return;
445
+ }
446
+ await sock.groupParticipantsUpdate(from, [target], 'remove');
447
+ await sock.sendMessage(from, { text: `✓ User kicked`, mentions: [target] });
448
+ }
449
+
450
+ async function handlePromote(
451
+ sock: ReturnType<typeof makeWASocket>,
452
+ from: JIDLike,
453
+ args: string[],
454
+ message: WAMessage,
455
+ promote: boolean
456
+ ) {
457
+ const target = await extractMentionedUser(sock, args, message);
458
+ if (!target) {
459
+ await sock.sendMessage(from, {
460
+ text: `Usage: !${promote ? 'promote' : 'demote'} @user`
461
+ });
462
+ return;
463
+ }
464
+ await sock.groupParticipantsUpdate(from, [target], promote ? 'promote' : 'demote');
465
+ }
466
+
467
+ async function handleTagAll(
468
+ sock: ReturnType<typeof makeWASocket>,
469
+ from: JIDLike,
470
+ metadata: WAGroupMetadata | undefined,
471
+ sender: JIDLike
472
+ ) {
473
+ if (!metadata) return;
474
+ const now = Date.now();
475
+ const last = lastTagAll.get(jidToString(from)) ?? 0;
476
+ if (now - last < CONFIG.tagAllCooldownMs) {
477
+ await sock.sendMessage(from, {
478
+ text: `Cooldown! Tunggu ${Math.ceil((CONFIG.tagAllCooldownMs - (now - last)) / 1000)} detik`
479
+ });
480
+ return;
481
+ }
482
+ lastTagAll.set(jidToString(from), now);
483
+
484
+ const mentions = metadata.participants.map(p => p.id);
485
+ const text = `*Tag All*\n\n` + mentions.map(m => `@${jidToString(m).split('@')[0]}`).join(' ');
486
+ await sock.sendMessage(from, { text, mentions });
487
+ }
488
+
489
+ async function handleHideTag(
490
+ sock: ReturnType<typeof makeWASocket>,
491
+ from: JIDLike,
492
+ text: string,
493
+ metadata: WAGroupMetadata | undefined
494
+ ) {
495
+ if (!metadata || !text) {
496
+ await sock.sendMessage(from, { text: 'Usage: !hidetag <message>' });
497
+ return;
498
+ }
499
+ const mentions = metadata.participants.map(p => p.id);
500
+ await sock.sendMessage(from, { text, mentions });
501
+ }
502
+
503
+ async function handlePoll(
504
+ sock: ReturnType<typeof makeWASocket>,
505
+ from: JIDLike,
506
+ args: string[]
507
+ ) {
508
+ const text = args.join(' ');
509
+ if (!text || !text.includes('|')) {
510
+ await sock.sendMessage(from, { text: 'Usage: !poll <question> | opt1 | opt2 | opt3' });
511
+ return;
512
+ }
513
+ const parts = text.split('|').map(p => p.trim());
514
+ const question = parts[0];
515
+ const options = parts.slice(1);
516
+ if (options.length < 2) {
517
+ await sock.sendMessage(from, { text: 'Minimal 2 options!' });
518
+ return;
519
+ }
520
+ // Note: This requires PollManager integration on WASocket
521
+ // For now, send as text
522
+ await sock.sendMessage(from, {
523
+ text: `*Poll: ${question}*\n\n${options.map((o, i) => `${i + 1}. ${o}`).join('\n')}`
524
+ });
525
+ }
526
+
527
+ async function extractMentionedUser(
528
+ sock: ReturnType<typeof makeWASocket>,
529
+ args: string[],
530
+ message: WAMessage
531
+ ): Promise<JIDLike | null> {
532
+ // Check mentioned jids dalam message
533
+ if (message.message?.contextInfo?.mentionedJid && message.message.contextInfo.mentionedJid.length > 0) {
534
+ return message.message.contextInfo.mentionedJid[0];
535
+ }
536
+ // Check args (phone number)
537
+ if (args.length > 0 && args[0].startsWith('@')) {
538
+ const phone = args[0].slice(1).replace(/[^0-9]/g, '');
539
+ return `${phone}@s.whatsapp.net`;
540
+ }
541
+ // Check quoted message participant
542
+ if (message.message?.contextInfo?.participant) {
543
+ return message.message.contextInfo.participant;
544
+ }
545
+ return null;
546
+ }
547
+
548
+ function jidUser(jid: string): string {
549
+ return jid.split('@')[0].split(':')[0];
550
+ }
551
+
552
+ // ============== Run ==============
553
+
554
+ main().catch(err => {
555
+ console.error('Fatal error:', err);
556
+ process.exit(1);
557
+ });
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "waliwa-group-bot",
3
+ "version": "1.0.0",
4
+ "description": "Group management bot dengan admin commands, anti-spam, welcome messages",
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
+ }