@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.
- package/LICENSE +21 -0
- package/README.md +600 -0
- package/dist/advanced/features.d.ts +211 -0
- package/dist/advanced/features.d.ts.map +1 -0
- package/dist/advanced/features.js +671 -0
- package/dist/advanced/features.js.map +1 -0
- package/dist/auth/auth-state.d.ts +89 -0
- package/dist/auth/auth-state.d.ts.map +1 -0
- package/dist/auth/auth-state.js +330 -0
- package/dist/auth/auth-state.js.map +1 -0
- package/dist/auth/pairing-code.d.ts +78 -0
- package/dist/auth/pairing-code.d.ts.map +1 -0
- package/dist/auth/pairing-code.js +191 -0
- package/dist/auth/pairing-code.js.map +1 -0
- package/dist/auth/qr-code.d.ts +60 -0
- package/dist/auth/qr-code.d.ts.map +1 -0
- package/dist/auth/qr-code.js +151 -0
- package/dist/auth/qr-code.js.map +1 -0
- package/dist/calls/handler.d.ts +61 -0
- package/dist/calls/handler.d.ts.map +1 -0
- package/dist/calls/handler.js +117 -0
- package/dist/calls/handler.js.map +1 -0
- package/dist/core/binary.d.ts +74 -0
- package/dist/core/binary.d.ts.map +1 -0
- package/dist/core/binary.js +448 -0
- package/dist/core/binary.js.map +1 -0
- package/dist/core/crypto.d.ts +60 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +170 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/noise.d.ts +106 -0
- package/dist/core/noise.d.ts.map +1 -0
- package/dist/core/noise.js +307 -0
- package/dist/core/noise.js.map +1 -0
- package/dist/events/emitter.d.ts +44 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/emitter.js +79 -0
- package/dist/events/emitter.js.map +1 -0
- package/dist/features/index.d.ts +342 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +755 -0
- package/dist/features/index.js.map +1 -0
- package/dist/fixes/index.d.ts +333 -0
- package/dist/fixes/index.d.ts.map +1 -0
- package/dist/fixes/index.js +762 -0
- package/dist/fixes/index.js.map +1 -0
- package/dist/groups/management.d.ts +86 -0
- package/dist/groups/management.d.ts.map +1 -0
- package/dist/groups/management.js +443 -0
- package/dist/groups/management.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +236 -0
- package/dist/index.js.map +1 -0
- package/dist/messages/media.d.ts +93 -0
- package/dist/messages/media.d.ts.map +1 -0
- package/dist/messages/media.js +252 -0
- package/dist/messages/media.js.map +1 -0
- package/dist/messages/message-encoder.d.ts +70 -0
- package/dist/messages/message-encoder.d.ts.map +1 -0
- package/dist/messages/message-encoder.js +453 -0
- package/dist/messages/message-encoder.js.map +1 -0
- package/dist/messages/send.d.ts +101 -0
- package/dist/messages/send.d.ts.map +1 -0
- package/dist/messages/send.js +409 -0
- package/dist/messages/send.js.map +1 -0
- package/dist/recovery/index.d.ts +125 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +584 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/skdm/index.d.ts +220 -0
- package/dist/skdm/index.d.ts.map +1 -0
- package/dist/skdm/index.js +600 -0
- package/dist/skdm/index.js.map +1 -0
- package/dist/socket/hybrid.d.ts +118 -0
- package/dist/socket/hybrid.d.ts.map +1 -0
- package/dist/socket/hybrid.js +352 -0
- package/dist/socket/hybrid.js.map +1 -0
- package/dist/socket/wa-socket.d.ts +300 -0
- package/dist/socket/wa-socket.d.ts.map +1 -0
- package/dist/socket/wa-socket.js +1094 -0
- package/dist/socket/wa-socket.js.map +1 -0
- package/dist/socket/ws-socket.d.ts +96 -0
- package/dist/socket/ws-socket.d.ts.map +1 -0
- package/dist/socket/ws-socket.js +302 -0
- package/dist/socket/ws-socket.js.map +1 -0
- package/dist/types/index.d.ts +444 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/jid.d.ts +46 -0
- package/dist/utils/jid.d.ts.map +1 -0
- package/dist/utils/jid.js +152 -0
- package/dist/utils/jid.js.map +1 -0
- package/dist/utils/logger.d.ts +45 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +79 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +43 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +175 -0
- package/dist/utils/retry.js.map +1 -0
- package/docs/API.md +745 -0
- package/docs/ARCHITECTURE.md +307 -0
- package/docs/BAILEYS_FIXES.md +360 -0
- package/docs/FEATURES.md +532 -0
- package/docs/PROTOCOL.md +489 -0
- package/docs/RECOVERY.md +409 -0
- package/docs/SKDM.md +233 -0
- package/examples/ai-bot/index.ts +479 -0
- package/examples/ai-bot/package.json +17 -0
- package/examples/echo-bot/index.ts +171 -0
- package/examples/echo-bot/package.json +18 -0
- package/examples/group-bot/index.ts +557 -0
- package/examples/group-bot/package.json +17 -0
- package/examples/rest-gateway/index.ts +499 -0
- package/examples/rest-gateway/package.json +19 -0
- package/package.json +75 -0
- package/src/advanced/features.ts +817 -0
- package/src/auth/auth-state.ts +342 -0
- package/src/auth/pairing-code.ts +246 -0
- package/src/auth/qr-code.ts +191 -0
- package/src/calls/handler.ts +153 -0
- package/src/core/binary.ts +464 -0
- package/src/core/crypto.ts +189 -0
- package/src/core/noise.ts +406 -0
- package/src/events/emitter.ts +88 -0
- package/src/features/index.ts +921 -0
- package/src/fixes/index.ts +882 -0
- package/src/groups/management.ts +497 -0
- package/src/index.ts +274 -0
- package/src/messages/media.ts +372 -0
- package/src/messages/message-encoder.ts +520 -0
- package/src/messages/send.ts +521 -0
- package/src/recovery/index.ts +704 -0
- package/src/skdm/index.ts +693 -0
- package/src/socket/hybrid.ts +414 -0
- package/src/socket/wa-socket.ts +1347 -0
- package/src/socket/ws-socket.ts +355 -0
- package/src/types/index.ts +457 -0
- package/src/utils/jid.ts +156 -0
- package/src/utils/logger.ts +84 -0
- package/src/utils/retry.ts +205 -0
- 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
|
+
}
|