dc-securex 2.15.3__py3-none-any.whl
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.
Potentially problematic release.
This version of dc-securex might be problematic. Click here for more details.
- dc_securex-2.15.3.dist-info/METADATA +653 -0
- dc_securex-2.15.3.dist-info/RECORD +17 -0
- dc_securex-2.15.3.dist-info/WHEEL +5 -0
- dc_securex-2.15.3.dist-info/licenses/LICENSE +21 -0
- dc_securex-2.15.3.dist-info/top_level.txt +1 -0
- securex/__init__.py +18 -0
- securex/backup/__init__.py +5 -0
- securex/backup/manager.py +693 -0
- securex/client.py +556 -0
- securex/handlers/__init__.py +8 -0
- securex/handlers/channel.py +97 -0
- securex/handlers/member.py +110 -0
- securex/handlers/role.py +74 -0
- securex/models.py +123 -0
- securex/utils/__init__.py +5 -0
- securex/utils/punishment.py +124 -0
- securex/utils/whitelist.py +129 -0
securex/client.py
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SecureX SDK - Main Client
|
|
3
|
+
Backend-only anti-nuke protection system
|
|
4
|
+
"""
|
|
5
|
+
import discord
|
|
6
|
+
import asyncio
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List, Callable, Optional
|
|
9
|
+
from .backup.manager import BackupManager
|
|
10
|
+
from .handlers.channel import ChannelHandler
|
|
11
|
+
from .handlers.role import RoleHandler
|
|
12
|
+
from .handlers.member import MemberHandler
|
|
13
|
+
from .utils.whitelist import WhitelistManager
|
|
14
|
+
from .utils.punishment import PunishmentExecutor
|
|
15
|
+
from .models import ThreatEvent, BackupInfo
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SecureX:
|
|
19
|
+
"""
|
|
20
|
+
Main SecureX SDK class for anti-nuke protection.
|
|
21
|
+
Backend-only - no UI, no commands, just pure protection logic.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
bot: discord.Client,
|
|
27
|
+
backup_dir: str = "./data/backups",
|
|
28
|
+
punishments: Optional[Dict[str, str]] = None,
|
|
29
|
+
timeout_duration: int = 600,
|
|
30
|
+
notify_user: bool = True
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Initialize SecureX SDK.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
bot: Your discord.Client or commands.Bot instance
|
|
37
|
+
backup_dir: Directory to store backups (default: ./data/backups)
|
|
38
|
+
punishments: Dict mapping violation types to punishment actions
|
|
39
|
+
timeout_duration: Duration in seconds for timeout punishment (default: 600)
|
|
40
|
+
notify_user: Whether to DM violators about punishment (default: True)
|
|
41
|
+
"""
|
|
42
|
+
self.bot = bot
|
|
43
|
+
self.backup_dir = Path(backup_dir)
|
|
44
|
+
self.backup_dir.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
self.backup_manager = BackupManager(self)
|
|
48
|
+
self.whitelist = WhitelistManager(self)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
self._callbacks: Dict[str, List[Callable]] = {
|
|
52
|
+
'threat_detected': [],
|
|
53
|
+
'backup_completed': [],
|
|
54
|
+
'restore_completed': [],
|
|
55
|
+
'whitelist_changed': [],
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
self.punishments = {
|
|
60
|
+
|
|
61
|
+
"bot_add": "none",
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
"channel_create": "none",
|
|
65
|
+
"channel_delete": "none",
|
|
66
|
+
"channel_update": "none",
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
"role_create": "none",
|
|
70
|
+
"role_delete": "none",
|
|
71
|
+
"role_update": "none",
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
"member_ban": "none",
|
|
75
|
+
"member_kick": "none",
|
|
76
|
+
"member_unban": "none",
|
|
77
|
+
"member_update": "none",
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
"webhook_create": "none",
|
|
81
|
+
"webhook_delete": "none",
|
|
82
|
+
"webhook_update": "none"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
self.punisher = PunishmentExecutor(bot)
|
|
87
|
+
self.timeout_duration = timeout_duration
|
|
88
|
+
self.notify_punished_user = notify_user
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
self.action_queue = asyncio.Queue(maxsize=1000)
|
|
92
|
+
self.log_queue = asyncio.Queue(maxsize=1000)
|
|
93
|
+
self.cleanup_queue = asyncio.Queue(maxsize=1000)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
self.whitelist_cache = {}
|
|
97
|
+
self.punishment_cache = {}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
self._worker_tasks = []
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
self.channel_handler = ChannelHandler(self)
|
|
104
|
+
self.role_handler = RoleHandler(self)
|
|
105
|
+
self.member_handler = MemberHandler(self)
|
|
106
|
+
self.webhook_handler = WebhookHandler(self)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
self._register_audit_log_listener()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
self._register_events()
|
|
113
|
+
|
|
114
|
+
def _register_audit_log_listener(self):
|
|
115
|
+
"""Register INSTANT audit log event listener (v2.0 - 5-10ms response)"""
|
|
116
|
+
@self.bot.event
|
|
117
|
+
async def on_audit_log_entry_create(entry: discord.AuditLogEntry):
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
|
|
121
|
+
await self.action_queue.put(entry)
|
|
122
|
+
await self.log_queue.put(entry)
|
|
123
|
+
await self.cleanup_queue.put(entry)
|
|
124
|
+
except asyncio.QueueFull as e:
|
|
125
|
+
print(f"⚠️ Queue full: {e}")
|
|
126
|
+
|
|
127
|
+
async def _action_worker(self):
|
|
128
|
+
"""Worker 1: Process audit log entries and punish instantly"""
|
|
129
|
+
print("🔧 Action Worker started")
|
|
130
|
+
while True:
|
|
131
|
+
try:
|
|
132
|
+
|
|
133
|
+
entry = await self.action_queue.get()
|
|
134
|
+
|
|
135
|
+
guild = entry.guild
|
|
136
|
+
executor = entry.user
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if executor.id == self.bot.user.id:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if executor.id == guild.owner_id:
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
whitelist_set = self.whitelist_cache.get(guild.id, set())
|
|
148
|
+
if executor.id in whitelist_set:
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
guild_punishments = self.punishment_cache.get(guild.id, self.punishments)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
action_map = {
|
|
156
|
+
|
|
157
|
+
discord.AuditLogAction.bot_add: "bot_add",
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
discord.AuditLogAction.channel_create: "channel_create",
|
|
161
|
+
discord.AuditLogAction.channel_delete: "channel_delete",
|
|
162
|
+
discord.AuditLogAction.channel_update: "channel_update",
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
discord.AuditLogAction.role_create: "role_create",
|
|
166
|
+
discord.AuditLogAction.role_delete: "role_delete",
|
|
167
|
+
discord.AuditLogAction.role_update: "role_update",
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
discord.AuditLogAction.kick: "member_kick",
|
|
171
|
+
discord.AuditLogAction.ban: "member_ban",
|
|
172
|
+
discord.AuditLogAction.unban: "member_unban",
|
|
173
|
+
discord.AuditLogAction.member_update: "member_update",
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
discord.AuditLogAction.webhook_create: "webhook_create",
|
|
177
|
+
discord.AuditLogAction.webhook_delete: "webhook_delete",
|
|
178
|
+
discord.AuditLogAction.webhook_update: "webhook_update",
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
violation_type = action_map.get(entry.action)
|
|
184
|
+
if not violation_type:
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
punishment_action = guild_punishments.get(violation_type, "none")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
punishment_result = "none"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if violation_type == "bot_add":
|
|
195
|
+
try:
|
|
196
|
+
bot_member = guild.get_member(entry.target.id)
|
|
197
|
+
if bot_member:
|
|
198
|
+
await bot_member.ban(reason="SecureX: Unauthorized bot addition")
|
|
199
|
+
punishment_result = "banned_bot"
|
|
200
|
+
print(f"🚀 INSTANT: Banned bot {entry.target.name} (added by {executor.name})")
|
|
201
|
+
except Exception as e:
|
|
202
|
+
print(f"Error banning bot: {e}")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
if violation_type in ["member_kick", "member_ban", "member_unban","webhook_create"]:
|
|
206
|
+
try:
|
|
207
|
+
await guild.ban(executor, reason=f"SecureX: Unauthorized {violation_type}")
|
|
208
|
+
punishment_result = "banned_violator"
|
|
209
|
+
print(f"🔨 INSTANT: Banned {executor.name} for {violation_type}")
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print(f"Error banning violator: {e}")
|
|
212
|
+
|
|
213
|
+
if punishment_action != "none":
|
|
214
|
+
try:
|
|
215
|
+
punishment_result = await self.punisher.punish(
|
|
216
|
+
guild=guild,
|
|
217
|
+
user=executor,
|
|
218
|
+
violation_type=violation_type,
|
|
219
|
+
details=f"{violation_type} on {getattr(entry.target, 'name', 'Unknown')}",
|
|
220
|
+
sdk=self
|
|
221
|
+
)
|
|
222
|
+
print(f"👢 {punishment_result.title()} {executor.name} for {violation_type}")
|
|
223
|
+
except Exception as e:
|
|
224
|
+
print(f"Cannot punish {executor.name}: {e}")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
print(f"Error in action worker: {e}")
|
|
230
|
+
|
|
231
|
+
async def _cleanup_worker(self):
|
|
232
|
+
"""Worker 1.5: Process audit log entries and delete unauthorized creations instantly"""
|
|
233
|
+
print("🧹 Cleanup Worker started")
|
|
234
|
+
while True:
|
|
235
|
+
try:
|
|
236
|
+
|
|
237
|
+
entry = await self.cleanup_queue.get()
|
|
238
|
+
|
|
239
|
+
guild = entry.guild
|
|
240
|
+
executor = entry.user
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
if executor.id == self.bot.user.id:
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if executor.id == guild.owner_id:
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
whitelist_set = self.whitelist_cache.get(guild.id, set())
|
|
252
|
+
if executor.id in whitelist_set:
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
if entry.action not in [
|
|
257
|
+
discord.AuditLogAction.channel_create,
|
|
258
|
+
discord.AuditLogAction.role_create,
|
|
259
|
+
discord.AuditLogAction.webhook_create
|
|
260
|
+
]:
|
|
261
|
+
continue
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
target = entry.target
|
|
266
|
+
if not target:
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
if entry.action == discord.AuditLogAction.role_create:
|
|
271
|
+
role = guild.get_role(target.id)
|
|
272
|
+
if role:
|
|
273
|
+
await role.delete(reason="SecureX: Unauthorized role creation")
|
|
274
|
+
print(f"🗑️ [Worker] Deleted unauthorized role: {role.name}")
|
|
275
|
+
|
|
276
|
+
elif entry.action == discord.AuditLogAction.channel_create:
|
|
277
|
+
channel = guild.get_channel(target.id)
|
|
278
|
+
if channel:
|
|
279
|
+
await channel.delete(reason="SecureX: Unauthorized channel creation")
|
|
280
|
+
print(f"🗑️ [Worker] Deleted unauthorized channel: {channel.name}")
|
|
281
|
+
|
|
282
|
+
elif entry.action == discord.AuditLogAction.webhook_create:
|
|
283
|
+
|
|
284
|
+
webhooks = await guild.webhooks()
|
|
285
|
+
for webhook in webhooks:
|
|
286
|
+
if webhook.id == target.id:
|
|
287
|
+
await webhook.delete(reason="SecureX: Unauthorized webhook creation")
|
|
288
|
+
print(f"🗑️ [Worker] Deleted unauthorized webhook: {webhook.name}")
|
|
289
|
+
break
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
except (discord.Forbidden, discord.NotFound):
|
|
293
|
+
pass
|
|
294
|
+
except Exception as e:
|
|
295
|
+
print(f"Cleanup error: {e}")
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
print(f"Error in cleanup worker: {e}")
|
|
299
|
+
|
|
300
|
+
async def _log_worker(self):
|
|
301
|
+
"""Worker 2: Write logs to DB/webhooks - completely independent"""
|
|
302
|
+
print("📝 Log Worker started")
|
|
303
|
+
while True:
|
|
304
|
+
try:
|
|
305
|
+
|
|
306
|
+
entry = await self.log_queue.get()
|
|
307
|
+
|
|
308
|
+
guild = entry.guild
|
|
309
|
+
executor = entry.user
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
if executor.id == self.bot.user.id or executor.id == guild.owner_id:
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
action_map = {
|
|
317
|
+
|
|
318
|
+
discord.AuditLogAction.bot_add: "bot_add",
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
discord.AuditLogAction.channel_create: "channel_create",
|
|
322
|
+
discord.AuditLogAction.channel_delete: "channel_delete",
|
|
323
|
+
discord.AuditLogAction.channel_update: "channel_update",
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
discord.AuditLogAction.role_create: "role_create",
|
|
327
|
+
discord.AuditLogAction.role_delete: "role_delete",
|
|
328
|
+
discord.AuditLogAction.role_update: "role_update",
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
discord.AuditLogAction.kick: "member_kick",
|
|
332
|
+
discord.AuditLogAction.ban: "member_ban",
|
|
333
|
+
discord.AuditLogAction.unban: "member_unban",
|
|
334
|
+
discord.AuditLogAction.member_update: "member_update",
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
discord.AuditLogAction.webhook_create: "webhook_create",
|
|
338
|
+
discord.AuditLogAction.webhook_delete: "webhook_delete",
|
|
339
|
+
discord.AuditLogAction.webhook_update: "webhook_update",
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
violation_type = action_map.get(entry.action)
|
|
343
|
+
if not violation_type:
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
threat = ThreatEvent(
|
|
348
|
+
type=violation_type,
|
|
349
|
+
guild_id=guild.id,
|
|
350
|
+
actor_id=executor.id,
|
|
351
|
+
target_id=entry.target.id if entry.target else None,
|
|
352
|
+
target_name=getattr(entry.target, 'name', 'Unknown'),
|
|
353
|
+
prevented=True,
|
|
354
|
+
restored=False,
|
|
355
|
+
punishment_action="logged"
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
await self._trigger_callbacks('threat_detected', threat)
|
|
360
|
+
|
|
361
|
+
except Exception as e:
|
|
362
|
+
print(f"Error in log worker: {e}")
|
|
363
|
+
|
|
364
|
+
def _register_events(self):
|
|
365
|
+
"""Register Discord event listeners for restorations"""
|
|
366
|
+
|
|
367
|
+
@self.bot.event
|
|
368
|
+
async def on_guild_channel_delete(channel):
|
|
369
|
+
await self.channel_handler.on_channel_delete(channel)
|
|
370
|
+
|
|
371
|
+
@self.bot.event
|
|
372
|
+
async def on_guild_channel_update(before, after):
|
|
373
|
+
await self.channel_handler.on_channel_update(before, after)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@self.bot.event
|
|
377
|
+
async def on_guild_role_delete(role):
|
|
378
|
+
await self.role_handler.on_role_delete(role)
|
|
379
|
+
|
|
380
|
+
@self.bot.event
|
|
381
|
+
async def on_guild_role_update(before, after):
|
|
382
|
+
await self.role_handler.on_role_update(before, after)
|
|
383
|
+
|
|
384
|
+
@self.bot.event
|
|
385
|
+
async def on_member_ban(guild, user):
|
|
386
|
+
await self.member_handler.on_member_ban(guild, user)
|
|
387
|
+
|
|
388
|
+
@self.bot.event
|
|
389
|
+
async def on_member_update(before, after):
|
|
390
|
+
await self.member_handler.on_member_update(before, after)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def on(self, event_name: str):
|
|
394
|
+
"""
|
|
395
|
+
Decorator to register callbacks for SDK events.
|
|
396
|
+
|
|
397
|
+
Usage:
|
|
398
|
+
@sx.on('threat_detected')
|
|
399
|
+
async def handle_threat(event: ThreatEvent):
|
|
400
|
+
print(f"Threat: {event.type}")
|
|
401
|
+
"""
|
|
402
|
+
def decorator(func: Callable):
|
|
403
|
+
if event_name in self._callbacks:
|
|
404
|
+
self._callbacks[event_name].append(func)
|
|
405
|
+
return func
|
|
406
|
+
return decorator
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
def on_threat_detected(self):
|
|
410
|
+
"""Convenience decorator for threat_detected events"""
|
|
411
|
+
return self.on('threat_detected')
|
|
412
|
+
|
|
413
|
+
@property
|
|
414
|
+
def on_backup_completed(self):
|
|
415
|
+
"""Convenience decorator for backup_completed events"""
|
|
416
|
+
return self.on('backup_completed')
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def on_restore_completed(self):
|
|
420
|
+
"""Convenience decorator for restore_completed events"""
|
|
421
|
+
return self.on('restore_completed')
|
|
422
|
+
|
|
423
|
+
@property
|
|
424
|
+
def on_whitelist_changed(self):
|
|
425
|
+
"""Convenience decorator for whitelist_changed events"""
|
|
426
|
+
return self.on('whitelist_changed')
|
|
427
|
+
|
|
428
|
+
async def _trigger_callbacks(self, event_name: str, *args, **kwargs):
|
|
429
|
+
"""Trigger all registered callbacks for an event"""
|
|
430
|
+
if event_name in self._callbacks:
|
|
431
|
+
for callback in self._callbacks[event_name]:
|
|
432
|
+
try:
|
|
433
|
+
if asyncio.iscoroutinefunction(callback):
|
|
434
|
+
await callback(*args, **kwargs)
|
|
435
|
+
else:
|
|
436
|
+
callback(*args, **kwargs)
|
|
437
|
+
except Exception as e:
|
|
438
|
+
print(f"Error in callback for {event_name}: {e}")
|
|
439
|
+
|
|
440
|
+
async def enable(
|
|
441
|
+
self,
|
|
442
|
+
guild_id: Optional[int] = None,
|
|
443
|
+
whitelist: Optional[List[int]] = None,
|
|
444
|
+
auto_backup: bool = True,
|
|
445
|
+
punishments: Optional[Dict[str, str]] = None,
|
|
446
|
+
timeout_duration: int = 600,
|
|
447
|
+
notify_user: bool = True
|
|
448
|
+
):
|
|
449
|
+
"""
|
|
450
|
+
Enable SecureX protection.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
guild_id: Guild ID to enable protection for (required if using whitelist)
|
|
454
|
+
whitelist: List of user IDs to whitelist (default: [])
|
|
455
|
+
auto_backup: Enable automatic backups every 30 minutes (default: True)
|
|
456
|
+
role_position_monitoring: Enable role position monitoring (default: True)
|
|
457
|
+
punishments: Dict mapping violation types to punishment actions
|
|
458
|
+
Example: {"channel_delete": "ban", "role_create": "kick"}
|
|
459
|
+
Available actions: "none", "warn", "timeout", "kick", "ban"
|
|
460
|
+
timeout_duration: Duration in seconds for timeout punishment (default: 600)
|
|
461
|
+
notify_user: Whether to DM violators about punishment (default: True)
|
|
462
|
+
"""
|
|
463
|
+
|
|
464
|
+
await self.whitelist.preload_all()
|
|
465
|
+
await self.backup_manager.preload_all()
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
if guild_id:
|
|
469
|
+
whitelist_data = await self.whitelist.get_all(guild_id)
|
|
470
|
+
self.whitelist_cache[guild_id] = set(whitelist_data)
|
|
471
|
+
print(f"📋 Loaded {len(whitelist_data)} whitelisted users into cache")
|
|
472
|
+
|
|
473
|
+
if whitelist and guild_id:
|
|
474
|
+
for user_id in whitelist:
|
|
475
|
+
await self.whitelist.add(guild_id, user_id)
|
|
476
|
+
|
|
477
|
+
self.whitelist_cache[guild_id] = self.whitelist_cache.get(guild_id, set()) | set(whitelist)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
if punishments:
|
|
481
|
+
self.punishments.update(punishments)
|
|
482
|
+
|
|
483
|
+
if guild_id:
|
|
484
|
+
self.punishment_cache[guild_id] = self.punishments.copy()
|
|
485
|
+
print(f"⚡ Punishment cache populated for guild {guild_id}")
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
self._worker_tasks.append(asyncio.create_task(self._action_worker()))
|
|
489
|
+
self._worker_tasks.append(asyncio.create_task(self._log_worker()))
|
|
490
|
+
self._worker_tasks.append(asyncio.create_task(self._cleanup_worker()))
|
|
491
|
+
print("🚀 Started 3 independent workers (Action, Log, Cleanup)")
|
|
492
|
+
print("⚡ Broadcast architecture - zero dependencies between workers")
|
|
493
|
+
|
|
494
|
+
if auto_backup:
|
|
495
|
+
|
|
496
|
+
self.backup_manager.start_auto_backup()
|
|
497
|
+
|
|
498
|
+
self.backup_manager.start_auto_refresh()
|
|
499
|
+
|
|
500
|
+
self.timeout_duration = timeout_duration
|
|
501
|
+
self.notify_punished_user = notify_user
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
if punishments:
|
|
505
|
+
enabled_punishments = {k: v for k, v in self.punishments.items() if v != "none"}
|
|
506
|
+
if enabled_punishments:
|
|
507
|
+
print(f"⚠️ Punishments configured for {len(enabled_punishments)} violation types")
|
|
508
|
+
|
|
509
|
+
print("✅ SecureX SDK v2.0 - Ultra-fast audit log system ACTIVE")
|
|
510
|
+
print("⚡ Response time: 5-10ms (100x faster than v1.x)")
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
if guild_id:
|
|
514
|
+
backup = await self.backup_manager.create_backup(guild_id)
|
|
515
|
+
if backup.success:
|
|
516
|
+
print(f"✅ Backup completed for guild {guild_id}: "
|
|
517
|
+
f"{backup.channel_count} channels, {backup.role_count} roles")
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
async def create_backup(self, guild_id: int) -> BackupInfo:
|
|
521
|
+
"""
|
|
522
|
+
Create a backup for the specified guild.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
guild_id: The guild ID to backup
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
BackupInfo object with backup results
|
|
529
|
+
"""
|
|
530
|
+
return await self.backup_manager.create_backup(guild_id)
|
|
531
|
+
|
|
532
|
+
async def restore_channel(self, guild: discord.Guild, channel: discord.abc.GuildChannel):
|
|
533
|
+
"""
|
|
534
|
+
Restore a deleted channel from backup.
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
guild: The guild object
|
|
538
|
+
channel: The deleted channel object
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
The newly created channel or None if restoration failed
|
|
542
|
+
"""
|
|
543
|
+
return await self.backup_manager.restore_channel(guild, channel)
|
|
544
|
+
|
|
545
|
+
async def restore_role(self, guild: discord.Guild, role_id: int) -> bool:
|
|
546
|
+
"""
|
|
547
|
+
Restore a deleted role from backup.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
guild: The guild object
|
|
551
|
+
role_id: The ID of the deleted role
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
True if successful, False otherwise
|
|
555
|
+
"""
|
|
556
|
+
return await self.backup_manager.restore_role(guild, role_id)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Handlers package - contains protection logic"""
|
|
2
|
+
|
|
3
|
+
from .channel import ChannelHandler
|
|
4
|
+
from .role import RoleHandler
|
|
5
|
+
from .member import MemberHandler
|
|
6
|
+
from .webhook import WebhookHandler
|
|
7
|
+
|
|
8
|
+
__all__ = ['ChannelHandler', 'RoleHandler', 'MemberHandler', 'WebhookHandler']
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Channel protection handler for SecureX SDK.
|
|
3
|
+
"""
|
|
4
|
+
import discord
|
|
5
|
+
import asyncio
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ChannelHandler:
|
|
10
|
+
"""Handles channel protection logic"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, sdk):
|
|
13
|
+
self.sdk = sdk
|
|
14
|
+
self.bot = sdk.bot
|
|
15
|
+
self.backup_manager = sdk.backup_manager
|
|
16
|
+
self.whitelist = sdk.whitelist
|
|
17
|
+
|
|
18
|
+
async def _is_authorized(self, guild: discord.Guild, user_id: int) -> bool:
|
|
19
|
+
"""Check if user is authorized (owner or whitelisted)"""
|
|
20
|
+
if guild.owner_id == user_id:
|
|
21
|
+
return True
|
|
22
|
+
if user_id == self.bot.user.id:
|
|
23
|
+
return True
|
|
24
|
+
return await self.whitelist.is_whitelisted(guild.id, user_id)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def on_channel_delete(self, channel: discord.abc.GuildChannel):
|
|
28
|
+
"""Restore unauthorized channel deletions"""
|
|
29
|
+
try:
|
|
30
|
+
guild = channel.guild
|
|
31
|
+
print(f"🔍 [Debug] Channel deleted: {channel.name} ({channel.id})")
|
|
32
|
+
await asyncio.sleep(1)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
from datetime import timezone
|
|
36
|
+
cutoff_time = datetime.now(timezone.utc) - timedelta(seconds=30)
|
|
37
|
+
|
|
38
|
+
found_entry = False
|
|
39
|
+
async for entry in guild.audit_logs(limit=50, action=discord.AuditLogAction.channel_delete):
|
|
40
|
+
if entry.created_at < cutoff_time:
|
|
41
|
+
break
|
|
42
|
+
|
|
43
|
+
if entry.target.id == channel.id:
|
|
44
|
+
found_entry = True
|
|
45
|
+
authorized = await self._is_authorized(guild, entry.user.id)
|
|
46
|
+
print(f"👤 [Debug] Executor: {entry.user.name} ({entry.user.id}) | Authorized: {authorized}")
|
|
47
|
+
|
|
48
|
+
if not authorized:
|
|
49
|
+
print(f"🛠️ [Debug] Unauthorized deletion detected. Triggering restoration...")
|
|
50
|
+
|
|
51
|
+
new_channel = await self.backup_manager.restore_channel(guild, channel)
|
|
52
|
+
if new_channel:
|
|
53
|
+
print(f"🔄 Restored unauthorized channel deletion: {channel.name}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if new_channel and isinstance(channel, discord.CategoryChannel):
|
|
57
|
+
await self.backup_manager.restore_category_children(
|
|
58
|
+
guild,
|
|
59
|
+
channel.id,
|
|
60
|
+
new_channel
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
print(f"✅ [Debug] Deletion by authorized user. Skipping restoration.")
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
if not found_entry:
|
|
67
|
+
print(f"⚠️ [Debug] Could not find audit log entry for channel deletion of {channel.name}")
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"❌ [Debug] Error in on_channel_delete: {e}")
|
|
71
|
+
|
|
72
|
+
async def on_channel_update(self, before: discord.abc.GuildChannel, after: discord.abc.GuildChannel):
|
|
73
|
+
"""Restore unauthorized channel permission changes"""
|
|
74
|
+
try:
|
|
75
|
+
|
|
76
|
+
if (before.overwrites == after.overwrites and
|
|
77
|
+
before.position == after.position):
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
guild = after.guild
|
|
81
|
+
await asyncio.sleep(0.5)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
from datetime import timezone
|
|
85
|
+
cutoff_time = datetime.now(timezone.utc) - timedelta(seconds=30)
|
|
86
|
+
|
|
87
|
+
async for entry in guild.audit_logs(limit=50, oldest_first=False):
|
|
88
|
+
if entry.created_at < cutoff_time:
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
if entry.action == discord.AuditLogAction.channel_update:
|
|
92
|
+
if entry.target and entry.target.id == after.id:
|
|
93
|
+
if not await self._is_authorized(guild, entry.user.id):
|
|
94
|
+
await self.backup_manager.restore_channel_permissions(guild, after.id)
|
|
95
|
+
break
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|