This repository was archived by the owner on Nov 8, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
1195 lines (1040 loc) · 50.1 KB
/
Copy pathindex.js
File metadata and controls
1195 lines (1040 loc) · 50.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* Discord Antinuke Selfbot (WebSocket Optimized)
* Using discord.js-selfbot-v13
*
* DISCLAIMER: Using selfbots is against Discord's Terms of Service.
* This code is provided for educational purposes only by faiz4sure.
* Use at your own risk. I do not take responsibility for any consequences.
*/
const { Client, WebhookClient } = require('discord.js-selfbot-v13');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const moment = require('moment');
const clear = require('clear');
// Load centralized configuration from config.yaml and .env
const config = require('./utils/ConfigLoader');
// Initialize whitelist manager
const WhitelistManager = require('./utils/WhitelistManager');
const whitelistManager = new WhitelistManager(config);
// Initialize rate limit handler
const rateLimitHandler = require('./utils/RateLimitHandler');
// Initialize client with optimized options for WebSocket
const client = new Client({
checkUpdate: false,
ws: {
properties: {
$browser: "Discord iOS" // Less likely to get detected as a bot
}
},
autoRedeemNitro: false,
patchVoice: false, // Disable unneeded voice functionality
syncStatus: false, // Disable status syncing to save resources
// RPC is controlled via config
presence: {
status: 'online',
afk: false
}
});
// ==================== RECENTACTIONS ====================
// Track recent actions to detect rapid malicious activities
const recentActions = {
bans: {},
kicks: {},
unbans: {}, // Added for AntiMassUnban
channelDeletions: {},
channelCreations: {},
roleDeletions: {},
roleCreations: {},
webhookCreations: {},
memberRoleUpdates: {}
};
// ==================== SERVER TRACKING ====================
// Track servers the bot was removed from
const removedServers = {
// Format: { serverId: { name: "server name", timestamp: Date.now(), reason: "kicked/left" } }
};
// Create data directory if it doesn't exist
const dataDir = path.join(__dirname, 'data');
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
// Create logs directory within data directory if it doesn't exist
const logsDir = path.join(dataDir, 'logs');
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
// Load removed servers from disk if the file exists
const removedServersPath = path.join(dataDir, 'removed_servers.json');
try {
if (fs.existsSync(removedServersPath)) {
const data = JSON.parse(fs.readFileSync(removedServersPath, 'utf8'));
Object.assign(removedServers, data);
console.log(chalk.yellow(`Loaded ${Object.keys(data).length} previously removed servers from disk`));
}
} catch (error) {
console.log(chalk.red(`Failed to load removed servers data: ${error.message}`));
}
// ==================== HELPER FUNCTIONS ====================
// Function to log actions specifically (bans, kicks, etc.) to actions.log
function logAction(action) {
const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
const actionsLogPath = path.join(dataDir, 'actions.log');
const logEntry = `[${timestamp}] ${action}\n`;
try {
fs.appendFileSync(actionsLogPath, logEntry);
// Only actions are shown in console as important notifications
console.log(`[${timestamp}] ${chalk.greenBright('✅')} ${action}`);
} catch (err) {
// If we can't write to actions log, log to error file
logError(`Failed to write to actions log: ${err.message}`);
}
}
// Function to log errors to errors.txt without console output
function logError(error) {
const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
const errorLogPath = path.join(logsDir, 'errors.txt');
const logEntry = `[${timestamp}] ${error}\n`;
try {
fs.appendFileSync(errorLogPath, logEntry);
// No console output for errors - they only go to file
} catch (err) {
// Last resort: if we can't even write to error log, only then show in console
console.error(`[${timestamp}] CRITICAL: Failed to write to error log: ${err.message}`);
}
}
// Log helper function - formats console output with timestamps and colors
function log(message, level = 'info', guildId = null) {
if (!config.logging) return;
// Log level filtering based on config
const logLevelPriority = {
'error': 1,
'warning': 2,
'success': 3,
'info': 4,
'debug': 5
};
// Only log messages that have priority <= configured log level
// This allows finer control over log verbosity
if (logLevelPriority[level] > logLevelPriority[config.logLevel]) {
return; // Skip this log message as it's below the configured priority
}
const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
let prefix = '';
let guildInfo = guildId ? ` [${client?.guilds.cache.get(guildId)?.name || guildId}]` : '';
// Special handling for errors - redirect to errors.txt
if (level === 'error') {
// Log error to errors.txt but not to console
logError(`${message}${guildInfo}`);
// Still log to daily file (in logs directory)
const today = moment().format('YYYY-MM-DD');
const logFilePath = path.join(logsDir, `${today}.logs`);
const logEntry = `[${timestamp}] [ERROR] ${message}${guildInfo}\n`;
try {
fs.appendFileSync(logFilePath, logEntry);
} catch (err) {
// If we can't write to daily log, at least it should be in errors.txt
}
return; // Skip normal console logging for errors
}
// For non-error levels, determine the console prefix
switch(level) {
case 'warning':
prefix = chalk.yellow('⚠️');
break;
case 'success':
prefix = chalk.green('✅');
break;
case 'debug':
prefix = chalk.magenta('🔍');
break;
case 'info':
default:
prefix = chalk.blue('ℹ️');
break;
}
// Console log (except for errors)
console.log(`[${timestamp}] ${prefix} ${message}${guildInfo}`);
// Log to daily file (in logs directory)
const today = moment().format('YYYY-MM-DD');
const logFilePath = path.join(logsDir, `${today}.logs`);
const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}${guildInfo}\n`;
try {
fs.appendFileSync(logFilePath, logEntry);
} catch (err) {
logError(`Failed to write to daily log file: ${err.message}`);
}
// Only send actual protection alerts and actions to webhook/DM, not status messages
const importantLevels = ['warning', 'error', 'success'];
if (guildId && importantLevels.includes(level)) {
// Only send actual alerts and actions (not basic info)
sendLogToChannel(guildId, message, level).catch(() => {
// Silently fail if we can't send log
});
}
}
// This function sends notifications through multiple channels:
// 1. Webhook with embeds if configured
// 2. DMs to server owners if enabled
// 3. Messages to a specific discord channel if configured
async function sendLogToChannel(guildId, message, level = 'info') {
// Get server name and context
const guild = client.guilds.cache.get(guildId);
const serverName = guild ? guild.name : guildId;
const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
// Determine embed color based on level
let color = 0x3498db; // Default blue for info
if (level === 'error' || level === 'warning') {
color = 0xe74c3c; // Red for errors/warnings
} else if (level === 'success') {
color = 0x2ecc71; // Green for success
}
// Create embed for rich formatting
const embed = {
color: color,
title: `Anti-Nuke Alert - ${serverName}`,
description: message,
timestamp: new Date(),
footer: {
text: `Discord AntiNuke Selfbot`,
icon_url: 'https://i.imgur.com/NN9S8J7.png'
},
fields: [
{
name: 'Server',
value: serverName,
inline: true
},
{
name: 'Time',
value: timestamp,
inline: true
}
]
};
// 1. Check if log_webhook is enabled in config
const webhookUrl = config.logWebhook;
if (webhookUrl && webhookUrl.length > 8) {
try {
// Embeds are allowed ONLY for webhooks, not for regular messages
const webhook = new WebhookClient({ url: webhookUrl });
await webhook.send({
embeds: [embed],
username: 'Discord Antinuke',
avatarURL: 'https://i.imgur.com/NN9S8J7.png' // Shield icon
});
webhook.destroy(); // Cleanup webhook client
log(`Successfully sent webhook notification`, 'info');
} catch (error) {
// Better error handling for webhook failures
if (error.code === 10015) {
log(`Webhook no longer exists: ${webhookUrl.substring(0, 20)}...`, 'error');
} else if (error.code === 50027) {
log(`Invalid webhook token: ${webhookUrl.substring(0, 20)}...`, 'error');
} else if (error.code === 429) {
log(`Rate limited when sending webhook - too many requests`, 'warning');
} else {
log(`Failed to send webhook: ${error.message}`, 'warning');
}
}
}
// 2. Check if DM to owners is enabled
if (config.logOwnerDm) {
// Format a plain text version of the message for DMs
const plainTextMessage = [
`**[${serverName}] Anti-Nuke Alert (${level.toUpperCase()})**`,
`${message}`,
`Time: ${timestamp}`
].join('\n');
// Keep track of successful DMs to avoid excessive error logging
let anyDmSuccessful = false;
let dmErrorsCount = 0;
for (const ownerId of config.ownerIds) {
try {
// Use a .catch() handler directly on fetch to avoid throwing errors
const owner = await client.users.fetch(ownerId)
.catch(() => {
// Silently handle fetch errors
return null;
});
// Skip if user can't be fetched
if (!owner) continue;
// Send a plain text DM (selfbots don't support embeds in DMs)
await owner.send(plainTextMessage);
anyDmSuccessful = true;
// Only log successful DMs in debug mode to reduce console spam
if (config.logLevel === 'debug') {
log(`Successfully sent alert to owner ${owner.tag}`, 'info');
}
} catch (error) {
// Count DM errors but don't log each one to avoid spam
dmErrorsCount++;
// Only log the first error if it's a critical alert (error or warning)
if (dmErrorsCount === 1 && ['error', 'warning'].includes(level)) {
// Use console.log directly instead of log() to avoid file logging
if (config.logLevel === 'debug') {
console.log(chalk.yellow(`Note: Cannot send DM to one or more owners - they likely have DMs closed`));
}
}
}
}
}
// 3. Send to a specific channel if configured
// With improved error handling to prevent errors when channel is not found
const channelId = config.logChannels[guildId];
if (channelId) {
// Format a plain text version of the message for channels
const plainTextMessage = [
`**Anti-Nuke Alert (${level.toUpperCase()})**`,
`${message}`,
`Server: ${serverName}`,
`Time: ${timestamp}`
].join('\n');
try {
// Wrapped in a silent try/catch to prevent any errors from propagating
const channel = await client.channels.fetch(channelId)
.catch(() => {
// Instead of logging an error, just return null and handle silently
return null;
});
if (channel && channel.isText()) {
try {
// Use another try/catch around send to handle permission issues silently
await channel.send(plainTextMessage);
log(`Successfully logged alert to channel #${channel.name}`, 'info');
} catch (sendError) {
// If we can't send to the channel, don't throw an error, just log silently
// and fall back to console-only logging
// Only log detailed error information in debug mode to reduce console spam
if (config.logLevel === 'debug') {
if (sendError.code === 50013) {
console.log(chalk.yellow(`Cannot send to log channel: Missing permissions`));
} else {
console.log(chalk.yellow(`Cannot send to log channel: ${sendError.message}`));
}
}
}
}
} catch (error) {
// Final catch-all to ensure any unexpected errors don't crash the bot
// Instead of logging errors about channels, we'll just silently continue
// This improves stability when log channels aren't available
}
}
}
/**
* Checks if a user is whitelisted
* @param {string} userId - The Discord user ID to check
* @param {string} guildId - The guild ID for logging context
* @returns {boolean} Whether the user is whitelisted
*/
function isWhitelisted(userId, guildId = null) {
// Validate input
if (!userId || typeof userId !== 'string') {
log(`Invalid user ID format checked against whitelist: ${userId}`, 'error', guildId);
return false;
}
// Use the WhitelistManager to check if user is whitelisted
const isInWhitelist = whitelistManager.isWhitelisted(userId);
// If this is a server owner ID check, log this for debugging
if (guildId && isInWhitelist) {
log(`User ${userId} is whitelisted and bypassing security checks`, 'info', guildId);
}
return isInWhitelist;
}
function isServerOwner(userId, guildId) {
const guild = client.guilds.cache.get(guildId);
return guild && guild.ownerId === userId;
}
function isProtectedServer(serverId) {
// ONLY protect servers explicitly listed in config.protectedServers
// If the array is empty, don't protect any servers
return config.protectedServers.includes(serverId);
}
function recordAction(actionType, userId, guildId) {
const now = Date.now();
// Initialize arrays if they don't exist
if (!recentActions[actionType][guildId]) {
recentActions[actionType][guildId] = [];
}
// Add action to the list
recentActions[actionType][guildId].push({
userId,
timestamp: now
});
// Log the current time window being used (only once per session and only in debug level)
if (!config.runtime.timeWindowLogged) {
const timeWindowMinutes = Math.floor(config.thresholds.timeWindow / 60000);
log(`Using time window of ${timeWindowMinutes} minutes for action tracking`, 'debug');
config.runtime.timeWindowLogged = true;
}
// Filter out old actions beyond the time window
recentActions[actionType][guildId] = recentActions[actionType][guildId].filter(
action => (now - action.timestamp) < config.thresholds.timeWindow
);
// Count actions by this user in the time period
const actionsByUser = recentActions[actionType][guildId].filter(
action => action.userId === userId
).length;
// Check if the threshold is exceeded
const threshold = config.thresholds[actionType] || 3; // Default threshold of 3
if (actionsByUser >= threshold) {
log(`ALERT: ${actionType} threshold exceeded by user ${userId} (${actionsByUser}/${threshold})`, 'warning', guildId);
return true; // Threshold exceeded
}
return false; // Threshold not exceeded
}
function formatUptime() {
const uptime = Date.now() - config.runtime.startTime;
const days = Math.floor(uptime / (1000 * 60 * 60 * 24));
const hours = Math.floor((uptime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((uptime % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((uptime % (1000 * 60)) / 1000);
const parts = [];
if (days > 0) parts.push(`${days}d`);
if (hours > 0) parts.push(`${hours}h`);
if (minutes > 0) parts.push(`${minutes}m`);
if (seconds > 0) parts.push(`${seconds}s`);
return parts.join(' ');
}
async function takeAction(userId, guildId, reason) {
try {
const guild = client.guilds.cache.get(guildId);
if (!guild) return;
// Use RateLimitHandler to avoid Discord API rate limits when fetching members
let member;
try {
member = await rateLimitHandler.execute(
`guild.${guildId}.members.fetch`,
async () => {
return await guild.members.fetch(userId);
},
[],
{ logFunction: (msg) => log(msg, 'debug') }
);
} catch (err) {
log(`Could not fetch member ${userId} for punishment: ${err.message}`, 'error', guildId);
return null;
}
if (!member) {
log(`Could not fetch member ${userId} for punishment`, 'error', guildId);
return;
}
// Make sure we're not trying to punish the server owner or ourselves
if (member.id === guild.ownerId || member.id === client.user.id) {
log(`Cannot punish server owner or self`, 'warning', guildId);
return;
}
// Check if we have permission to do this action
const me = guild.members.cache.get(client.user.id);
if (!me.permissions.has("BAN_MEMBERS") && config.punishment === 'ban') {
log(`No permission to ban members`, 'error', guildId);
return;
}
if (!me.permissions.has("KICK_MEMBERS") && config.punishment === 'kick') {
log(`No permission to kick members`, 'error', guildId);
return;
}
// Ensure we can manage this member (role hierarchy check)
if (member.roles.highest.position >= me.roles.highest.position) {
log(`Cannot ${config.punishment} member with equal or higher role`, 'warning', guildId);
return;
}
// Execute the punishment
const fullReason = `[AntiNuke] ${reason}`;
// Construct detailed message for logs and notifications
const actionDetails = {
user: `${member.user.tag} (${member.id})`,
action: config.punishment.toUpperCase(),
reason: reason,
server: guild.name,
timestamp: moment().format('YYYY-MM-DD HH:mm:ss')
};
// Execute the punishment
if (config.punishment === 'ban') {
try {
// Use RateLimitHandler to handle potential rate limits during banning
await rateLimitHandler.execute(
`guild.${guildId}.members.ban`,
async () => {
await member.ban({ reason: fullReason });
},
[],
{
logFunction: (msg) => log(msg, 'warning', guildId),
retryLimit: 3
}
);
const successMsg = `Banned ${actionDetails.user}: ${actionDetails.reason}`;
// Log to actions.log in data folder for easy review of actions taken
logAction(`[${guild.name}] Banned ${actionDetails.user}: ${actionDetails.reason}`);
log(successMsg, 'success', guildId);
} catch (error) {
log(`Failed to ban ${actionDetails.user}: ${error.message}`, 'error', guildId);
return;
}
// Direct notification to all server owners (irrespective of config.logOwnerDm)
// This ensures owners are always notified of bans
const actionMessage = [
`🛡️ **ANTI-NUKE ACTION TAKEN**`,
`A user has been banned for suspicious activity`,
``,
`**User:** ${actionDetails.user}`,
`**Action:** BANNED`,
`**Reason:** ${actionDetails.reason}`,
`**Server:** ${actionDetails.server}`,
`**Time:** ${actionDetails.timestamp}`
].join('\n');
// Always DM owners about actions taken
for (const ownerId of config.ownerIds) {
try {
// Rate limit handler for fetching users
const owner = await rateLimitHandler.execute(
`users.fetch.${ownerId}`,
async () => await client.users.fetch(ownerId),
[],
{ logFunction: (msg) => log(msg, 'debug') }
);
// Rate limit handler for sending DMs
await rateLimitHandler.execute(
`users.${ownerId}.send`,
async () => await owner.send(actionMessage),
[],
{ logFunction: (msg) => log(msg, 'debug') }
);
log(`Successfully notified owner ${ownerId} about ban action`, 'info');
} catch (error) {
// Better error handling for DM failures
if (error.code === 50007) {
log(`Cannot send ban notification to owner ${ownerId} - they have DMs closed`, 'warning', guildId);
} else {
log(`Failed to notify owner ${ownerId} about ban: ${error.message}`, 'warning', guildId);
}
}
}
} else if (config.punishment === 'kick') {
try {
// Use RateLimitHandler to handle potential rate limits during kicking
await rateLimitHandler.execute(
`guild.${guildId}.members.kick`,
async () => {
await member.kick(fullReason);
},
[],
{
logFunction: (msg) => log(msg, 'warning', guildId),
retryLimit: 3
}
);
const successMsg = `Kicked ${actionDetails.user}: ${actionDetails.reason}`;
// Log to actions.log in data folder for easy review of actions taken
logAction(`[${guild.name}] Kicked ${actionDetails.user}: ${actionDetails.reason}`);
log(successMsg, 'success', guildId);
} catch (error) {
log(`Failed to kick ${actionDetails.user}: ${error.message}`, 'error', guildId);
return;
}
// Same owner notification for kicks
const actionMessage = [
`🛡️ **ANTI-NUKE ACTION TAKEN**`,
`A user has been kicked for suspicious activity`,
``,
`**User:** ${actionDetails.user}`,
`**Action:** KICKED`,
`**Reason:** ${actionDetails.reason}`,
`**Server:** ${actionDetails.server}`,
`**Time:** ${actionDetails.timestamp}`
].join('\n');
// Always DM owners about actions taken
for (const ownerId of config.ownerIds) {
try {
// Rate limit handler for fetching users
const owner = await rateLimitHandler.execute(
`users.fetch.${ownerId}`,
async () => await client.users.fetch(ownerId),
[],
{ logFunction: (msg) => log(msg, 'debug') }
);
// Rate limit handler for sending DMs
await rateLimitHandler.execute(
`users.${ownerId}.send`,
async () => await owner.send(actionMessage),
[],
{ logFunction: (msg) => log(msg, 'debug') }
);
log(`Successfully notified owner ${ownerId} about kick action`, 'info');
} catch (error) {
// Better error handling for DM failures
if (error.code === 50007) {
log(`Cannot send kick notification to owner ${ownerId} - they have DMs closed`, 'warning', guildId);
} else {
log(`Failed to notify owner ${ownerId} about kick: ${error.message}`, 'warning', guildId);
}
}
}
} else {
// Just log if punishment is set to 'none'
log(`Detected malicious activity by ${actionDetails.user}: ${actionDetails.reason}`, 'warning', guildId);
}
} catch (error) {
log(`Error taking action against user ${userId}: ${error.message}`, 'error', guildId);
}
}
function cacheGuild(guild) {
try {
// Cache all channels and roles for faster access
const channelCount = guild.channels.cache.size;
const roleCount = guild.roles.cache.size;
// Ensure we have all members cached
// guild.members.fetch().catch(() => {}); // Full fetch is often rate-limited
log(`Cached ${guild.name} data: ${channelCount} channels, ${roleCount} roles`, 'info', guild.id);
} catch (error) {
log(`Error caching guild data for ${guild.name}: ${error.message}`, 'error', guild.id);
}
}
async function recoverChannel(channelId, guildId, deletedChannel) {
try {
const guild = client.guilds.cache.get(guildId);
if (!guild) {
log(`Cannot recover channel: Guild ${guildId} not found`, 'error', guildId);
return;
}
// Check if recovery is enabled in config
if (!config.antinuke_settings || !config.antinuke_settings.auto_recovery || !config.antinuke_settings.recover_channels) {
log(`Channel recovery skipped: Recovery disabled in config`, 'info', guildId);
return;
}
// First, try to get channel data from our cache (for future implementation)
// For now, we just use the basic info we have from the deletedChannel object
if (!deletedChannel) {
log(`Limited channel recovery: No channel data available for ${channelId}`, 'warning', guildId);
// Attempt basic recovery with minimal info
const newChannel = await guild.channels.create(`recovered-${channelId}`, {
type: 'GUILD_TEXT',
topic: `Recovered channel (ID: ${channelId})`
}).catch(e => {
log(`Error creating recovered channel: ${e.message}`, 'error', guildId);
return null;
});
if (newChannel) {
logAction(`[${guild.name}] Successfully recovered deleted channel with basic settings (ID: ${channelId})`);
log(`Created basic recovery channel ${newChannel.name} for deleted ${channelId}`, 'success', guildId);
// Send a message in the new channel
await newChannel.send({
content: `This channel was recovered after it was deleted by a non-whitelisted user. Original ID: ${channelId}`
}).catch(() => {});
return newChannel;
}
} else {
// We have the deleted channel data, so we can create a more accurate replica
log(`Detailed channel recovery for ${deletedChannel.name} (${channelId})`, 'info', guildId);
// Use rate limit handler for channel creation to prevent Discord API limits
// With improved error handling to prevent crashes
try {
await rateLimitHandler.execute('channelCreation', async () => {
// Add a small delay to prevent immediate throttling
await new Promise(resolve => setTimeout(resolve, config.antinuke_settings?.recovery_delay || 500));
return true;
}, [], {
// Only log rate limit messages in debug mode to avoid console spam
logFunction: (msg) => {
if (config.logLevel === 'debug') {
log(msg, 'info', guildId);
}
}
});
} catch (error) {
// Silently handle rate limit errors without stopping execution
// Just a small delay if something goes wrong
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Create a new channel with the same properties
const channelOptions = {
type: deletedChannel.type,
topic: deletedChannel.topic,
nsfw: deletedChannel.nsfw,
bitrate: deletedChannel.bitrate,
userLimit: deletedChannel.userLimit,
parent: deletedChannel.parent,
// Copy permission overwrites accurately
permissionOverwrites: Array.isArray(deletedChannel.permissionOverwrites)
? deletedChannel.permissionOverwrites
: (deletedChannel.permissionOverwrites?.cache?.map(overwrite => ({
id: overwrite.id,
type: overwrite.type,
allow: overwrite.allow,
deny: overwrite.deny
})) || [])
};
// Clean up undefined values
Object.keys(channelOptions).forEach(key => {
if (channelOptions[key] === undefined) delete channelOptions[key];
});
// Create the channel
const newChannel = await guild.channels.create(deletedChannel.name, channelOptions)
.catch(e => {
log(`Error recreating channel ${deletedChannel.name}: ${e.message}`, 'error', guildId);
return null;
});
if (newChannel) {
logAction(`[${guild.name}] Successfully recovered deleted channel ${deletedChannel.name} (ID: ${channelId})`);
log(`Recovered channel ${newChannel.name} with detailed settings`, 'success', guildId);
// Send a message in the new channel if it's a text channel
if (newChannel.type === 'GUILD_TEXT' || newChannel.type === 'GUILD_NEWS') {
await newChannel.send({
content: `This channel was recovered after it was deleted by a non-whitelisted user.\nOriginal ID: ${channelId}`
}).catch(() => {});
}
return newChannel;
}
}
return null;
} catch (error) {
log(`Error during channel recovery: ${error.message}`, 'error', guildId);
return null;
}
}
async function recoverRole(roleId, guildId, deletedRole) {
try {
const guild = client.guilds.cache.get(guildId);
if (!guild) {
log(`Cannot recover role: Guild ${guildId} not found`, 'error', guildId);
return;
}
// Check if recovery is enabled in config
if (!config.antinuke_settings || !config.antinuke_settings.auto_recovery || !config.antinuke_settings.recover_roles) {
log(`Role recovery skipped: Recovery disabled in config`, 'info', guildId);
return;
}
// First, try to get role data from our cache (for future implementation)
// For now, we just use the basic info we have from the deletedRole object
if (!deletedRole) {
log(`Limited role recovery: No role data available for ${roleId}`, 'warning', guildId);
// Attempt basic recovery with minimal info
const newRole = await guild.roles.create({
name: `recovered-${roleId}`,
color: 'GREY',
reason: `Auto-recovery of deleted role ${roleId}`
}).catch(e => {
log(`Error creating recovered role: ${e.message}`, 'error', guildId);
return null;
});
if (newRole) {
logAction(`[${guild.name}] Successfully recovered deleted role with basic settings (ID: ${roleId})`);
log(`Created basic recovery role ${newRole.name} for deleted ${roleId}`, 'success', guildId);
return newRole;
}
} else {
// We have the deleted role data, so we can create a more accurate replica
log(`Detailed role recovery for ${deletedRole.name} (${roleId})`, 'info', guildId);
// Use rate limit handler for role creation to prevent Discord API limits
// With improved error handling to prevent crashes
try {
await rateLimitHandler.execute('roleCreation', async () => {
// Add a small delay to prevent immediate throttling
await new Promise(resolve => setTimeout(resolve, config.antinuke_settings?.recovery_delay || 500));
return true;
}, [], {
// Only log rate limit messages in debug mode to avoid console spam
logFunction: (msg) => {
if (config.logLevel === 'debug') {
log(msg, 'info', guildId);
}
}
});
} catch (error) {
// Silently handle rate limit errors without stopping execution
// Just a small delay if something goes wrong
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Create a new role with the same properties
const roleOptions = {
name: deletedRole.name,
color: deletedRole.hexColor || 'GREY',
hoist: deletedRole.hoist,
mentionable: deletedRole.mentionable,
permissions: deletedRole.permissions,
position: deletedRole.position,
reason: `Auto-recovery of deleted role ${roleId}`
};
// Clean up undefined values
Object.keys(roleOptions).forEach(key => {
if (roleOptions[key] === undefined) delete roleOptions[key];
});
// Create the role
const newRole = await guild.roles.create(roleOptions)
.catch(e => {
log(`Error recreating role ${deletedRole.name}: ${e.message}`, 'error', guildId);
return null;
});
if (newRole) {
logAction(`[${guild.name}] Successfully recovered deleted role ${deletedRole.name} (ID: ${roleId})`);
log(`Recovered role ${newRole.name} with detailed settings`, 'success', guildId);
return newRole;
}
}
return null;
} catch (error) {
log(`Error during role recovery: ${error.message}`, 'error', guildId);
return null;
}
}
function displayStartupBanner() {
console.log('\n' + chalk.cyanBright('═════════════════════════════════════════════════════════════'));
console.log(chalk.whiteBright(' Discord Antinuke Selfbot'));
console.log(chalk.gray(' (WebSocket Optimized)'));
console.log(chalk.cyanBright('═════════════════════════════════════════════════════════════'));
console.log(chalk.yellowBright(' 🛡️ Protecting servers against nukes and raids'));
console.log(chalk.yellowBright(' ⚡ Optimized for speed and reliability'));
console.log(chalk.yellowBright(' 🔧 Built with discord.js-selfbot-v13'));
console.log(chalk.cyanBright('═════════════════════════════════════════════════════════════'));
console.log(chalk.gray(' • Using WebSocket connection'));
console.log(chalk.gray(' • Auto-recovery of deleted channels and roles'));
console.log(chalk.gray(' • Threshold-based detection to minimize false positives'));
console.log(chalk.cyanBright('═════════════════════════════════════════════════════════════'));
console.log(chalk.magentaBright(' • For help regarding any issue or setup:'));
console.log(chalk.blueBright(' https://discord.gg/PEphKsNpe8'));
console.log(chalk.cyanBright('═════════════════════════════════════════════════════════════'));
console.log(chalk.redBright(' Made with ❤️ by faiz4sure'));
console.log(chalk.cyanBright('═════════════════════════════════════════════════════════════\n'));
}
// Export the displayStartupBanner function so it can be used by other modules
module.exports.displayStartupBanner = displayStartupBanner;
// ==================== EVENT HANDLERS ====================
// Load event handlers (silently without logging each one)
const eventFiles = fs.readdirSync('./events').filter(file => file.endsWith('.js'));
const eventCount = eventFiles.length;
// Load events silently
for (const file of eventFiles) {
const event = require(`./events/${file}`);
client.on(event.name, (...args) => {
// Pass client and helper functions to each event handler
event.execute(client, ...args, {
config,
log,
isWhitelisted,
isServerOwner,
isProtectedServer,
recordAction,
takeAction,
recoverChannel,
recoverRole,
cacheGuild,
recentActions,
whitelistManager,
removedServers,
saveRemovedServers
});
});
}
// Load other handlers (anti-crash, permissions, etc.) silently
const handlersDir = './handlers';
const initializedHandlers = [];
if (fs.existsSync(handlersDir)) {
const handlerFiles = fs.readdirSync(handlersDir).filter(file => file.endsWith('.js'));
const handlerCount = handlerFiles.length;
for (const file of handlerFiles) {
const handler = require(`${handlersDir}/${file}`);
if (typeof handler.init === 'function') {
handler.init(client, {
config,
log,
isWhitelisted,
isServerOwner,
isProtectedServer,
recordAction,
takeAction,
recoverChannel,
recoverRole,
cacheGuild,
sendLogToChannel,
recentActions,
whitelistManager
});
// Remember handler name without logging
initializedHandlers.push(file.replace('.js', ''));
}
}
}
// No command handling as per requirements
// All configuration is done through config.yaml
// ==================== LOGIN ====================
// Create a visually appealing startup banner (initial, will be cleared after load)
console.log(chalk.blue('\nInitializing Discord Antinuke Selfbot...'));
console.log(chalk.gray('Loading modules and event handlers...'));
// Silent initialization - we'll show the full banner after login
// Check if token is provided
if (!config.token || config.token === 'YOUR_TOKEN_HERE' || config.token === '') {
console.log(chalk.redBright('❌ No Discord token provided!'));
console.log(chalk.yellowBright('Please set your token in a .env file or directly in the config.'));
console.log(chalk.gray('Example .env file: DISCORD_TOKEN=your_token_here'));
process.exit(1);
}
// Check if at least one owner ID is provided (silently)
const hasNoOwners = config.ownerIds.length === 0;
// Login with progress indicator
console.log(chalk.cyanBright('\n🔄 Connecting to Discord API...'));
// Display event and handler stats