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.

@@ -0,0 +1,110 @@
1
+ """
2
+ Member protection handler (bot/ban/kick protection).
3
+ """
4
+ import discord
5
+ import asyncio
6
+
7
+
8
+ class MemberHandler:
9
+ """Handles member-related protection"""
10
+
11
+
12
+ DANGEROUS_PERMISSIONS = frozenset([
13
+ 'administrator',
14
+ 'kick_members',
15
+ 'ban_members',
16
+ 'manage_guild',
17
+ 'manage_roles',
18
+ 'manage_channels',
19
+ 'manage_webhooks',
20
+ 'manage_emojis',
21
+ 'mention_everyone',
22
+ 'manage_expressions'
23
+ ])
24
+
25
+ def __init__(self, sdk):
26
+ self.sdk = sdk
27
+ self.bot = sdk.bot
28
+ self.whitelist = sdk.whitelist
29
+
30
+ async def _is_authorized(self, guild: discord.Guild, user_id: int) -> bool:
31
+ """Check if user is authorized"""
32
+ if guild.owner_id == user_id:
33
+ return True
34
+ if user_id == self.bot.user.id:
35
+ return True
36
+ return await self.whitelist.is_whitelisted(guild.id, user_id)
37
+
38
+ async def on_member_ban(self, guild: discord.Guild, user: discord.User):
39
+ """Restore banned victims (punishment handled by action worker)"""
40
+ try:
41
+ await asyncio.sleep(0.5)
42
+
43
+
44
+ from datetime import datetime, timezone, timedelta
45
+ cutoff_time = datetime.now(timezone.utc) - timedelta(seconds=30)
46
+
47
+ async for entry in guild.audit_logs(limit=50, action=discord.AuditLogAction.ban):
48
+ if entry.created_at < cutoff_time:
49
+ break
50
+
51
+ if entry.target.id == user.id:
52
+ if not await self._is_authorized(guild, entry.user.id):
53
+
54
+ try:
55
+ await guild.unban(user, reason="SecureX: Restoring banned member")
56
+ print(f"🔄 Unbanned unauthorized member ban: {user.name}")
57
+ except (discord.errors.Forbidden, discord.errors.HTTPException):
58
+ pass
59
+ break
60
+ except Exception as e:
61
+ print(f"Error in on_member_ban: {e}")
62
+
63
+ async def on_member_update(self, before: discord.Member, after: discord.Member):
64
+ """Check for dangerous permissions and remove unauthorized roles"""
65
+ try:
66
+
67
+ if before.roles == after.roles:
68
+ return
69
+
70
+ guild = after.guild
71
+ await asyncio.sleep(0.5)
72
+
73
+
74
+ from datetime import datetime, timezone, timedelta
75
+ cutoff_time = datetime.now(timezone.utc) - timedelta(seconds=30)
76
+
77
+ async for entry in guild.audit_logs(limit=50, action=discord.AuditLogAction.member_role_update):
78
+ if entry.created_at < cutoff_time:
79
+ break
80
+ if entry.target.id == after.id:
81
+
82
+ is_authorized = await self._is_authorized(guild, entry.user.id)
83
+
84
+ if not is_authorized:
85
+
86
+ dangerous_roles = []
87
+
88
+ for role in after.roles:
89
+ if role.is_default():
90
+ continue
91
+
92
+
93
+ permissions = role.permissions
94
+ for perm_name in self.DANGEROUS_PERMISSIONS:
95
+ if getattr(permissions, perm_name, False):
96
+ dangerous_roles.append(role)
97
+ break
98
+
99
+
100
+ if dangerous_roles:
101
+ try:
102
+ await after.remove_roles(*dangerous_roles, reason="SecureX: Removed dangerous roles (unauthorized update)")
103
+ role_names = ", ".join([r.name for r in dangerous_roles])
104
+ print(f"🛡️ Bulk removed {len(dangerous_roles)} dangerous role(s) from {after.name}: {role_names}")
105
+ except (discord.errors.Forbidden, discord.errors.HTTPException) as e:
106
+ print(f"⚠️ Failed to remove roles: {e}")
107
+
108
+ break
109
+ except Exception as e:
110
+ print(f"Error in on_member_update: {e}")
@@ -0,0 +1,74 @@
1
+ """
2
+ Role protection handler for SecureX SDK.
3
+ """
4
+ import discord
5
+ import asyncio
6
+ from datetime import datetime, timedelta
7
+
8
+
9
+ class RoleHandler:
10
+ """Handles role 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"""
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_role_delete(self, role: discord.Role):
28
+ """Restore unauthorized role deletions"""
29
+ try:
30
+ guild = role.guild
31
+ await asyncio.sleep(1)
32
+
33
+
34
+ from datetime import timezone
35
+ cutoff_time = datetime.now(timezone.utc) - timedelta(seconds=30)
36
+
37
+ async for entry in guild.audit_logs(limit=50, action=discord.AuditLogAction.role_delete):
38
+ if entry.created_at < cutoff_time:
39
+ break
40
+
41
+ if entry.target.id == role.id:
42
+ if not await self._is_authorized(guild, entry.user.id):
43
+ if await self.backup_manager.restore_role(guild, role.id):
44
+ print(f"🔄 Restored unauthorized role deletion: {role.name}")
45
+ break
46
+ except Exception:
47
+ pass
48
+
49
+ async def on_role_update(self, before: discord.Role, after: discord.Role):
50
+ """Restore unauthorized role permission changes"""
51
+ try:
52
+
53
+ if before.position == after.position and before.permissions == after.permissions:
54
+ return
55
+
56
+ guild = after.guild
57
+ await asyncio.sleep(0.3)
58
+
59
+
60
+ from datetime import timezone
61
+ cutoff_time = datetime.now(timezone.utc) - timedelta(seconds=30)
62
+
63
+ async for entry in guild.audit_logs(limit=50, oldest_first=False):
64
+ if entry.created_at < cutoff_time:
65
+ break
66
+
67
+ if entry.action == discord.AuditLogAction.role_update:
68
+ if entry.target.id == after.id:
69
+ if not await self._is_authorized(guild, entry.user.id):
70
+ if await self.backup_manager.restore_role_permissions(guild, after.id):
71
+ print(f"🔄 Restored unauthorized role update: {after.name}")
72
+ break
73
+ except Exception:
74
+ pass
securex/models.py ADDED
@@ -0,0 +1,123 @@
1
+ """
2
+ Data models for SecureX SDK.
3
+ These objects are returned to developers - no UI included.
4
+ """
5
+ from dataclasses import dataclass, asdict, field
6
+ from datetime import datetime
7
+ from typing import Optional, List, Dict
8
+ import json
9
+
10
+
11
+ @dataclass
12
+ class ThreatEvent:
13
+ """
14
+ Represents a detected security threat.
15
+
16
+ Attributes:
17
+ type: Type of threat (e.g., "channel_delete", "role_create")
18
+ guild_id: ID of the guild where threat occurred
19
+ actor_id: ID of user who performed the action
20
+ target_id: ID of the affected resource
21
+ target_name: Name of the affected resource
22
+ prevented: Whether the action was prevented
23
+ restored: Whether restoration was successful
24
+ timestamp: When the threat was detected
25
+ details: Additional context about the threat
26
+ """
27
+ type: str
28
+ guild_id: int
29
+ actor_id: int
30
+ target_id: int
31
+ target_name: str
32
+ prevented: bool
33
+ restored: bool
34
+ timestamp: datetime = field(default_factory=datetime.utcnow)
35
+ details: Dict = field(default_factory=dict)
36
+ punishment_action: Optional[str] = None
37
+
38
+ def to_dict(self) -> dict:
39
+ """Convert to dictionary for serialization"""
40
+ data = asdict(self)
41
+ data['timestamp'] = self.timestamp.isoformat()
42
+ return data
43
+
44
+ def to_json(self) -> str:
45
+ """Convert to JSON string"""
46
+ return json.dumps(self.to_dict())
47
+
48
+
49
+ @dataclass
50
+ class BackupInfo:
51
+ """
52
+ Information about a server backup.
53
+
54
+ Attributes:
55
+ guild_id: ID of the backed up guild
56
+ timestamp: When backup was created
57
+ channel_count: Number of channels backed up
58
+ role_count: Number of roles backed up
59
+ backup_path: Path to backup file
60
+ success: Whether backup completed successfully
61
+ """
62
+ guild_id: int
63
+ timestamp: datetime
64
+ channel_count: int
65
+ role_count: int
66
+ backup_path: str
67
+ success: bool = True
68
+
69
+ def to_dict(self) -> dict:
70
+ """Convert to dictionary"""
71
+ data = asdict(self)
72
+ data['timestamp'] = self.timestamp.isoformat()
73
+ return data
74
+
75
+
76
+ @dataclass
77
+ class RestoreResult:
78
+ """
79
+ Result of a restoration operation.
80
+
81
+ Attributes:
82
+ success: Overall success status
83
+ items_restored: Number of items successfully restored
84
+ items_failed: Number of items that failed to restore
85
+ errors: List of error messages
86
+ duration: Time taken in seconds
87
+ details: Additional restoration details
88
+ """
89
+ success: bool
90
+ items_restored: int
91
+ items_failed: int
92
+ errors: List[str] = field(default_factory=list)
93
+ duration: float = 0.0
94
+ details: Dict = field(default_factory=dict)
95
+
96
+ def to_dict(self) -> dict:
97
+ """Convert to dictionary"""
98
+ return asdict(self)
99
+
100
+
101
+ @dataclass
102
+ class WhitelistChange:
103
+ """
104
+ Represents a whitelist modification.
105
+
106
+ Attributes:
107
+ guild_id: Guild where change occurred
108
+ user_id: User added/removed from whitelist
109
+ action: "added" or "removed"
110
+ timestamp: When the change occurred
111
+ moderator_id: Who made the change
112
+ """
113
+ guild_id: int
114
+ user_id: int
115
+ action: str
116
+ timestamp: datetime = field(default_factory=datetime.utcnow)
117
+ moderator_id: Optional[int] = None
118
+
119
+ def to_dict(self) -> dict:
120
+ """Convert to dictionary"""
121
+ data = asdict(self)
122
+ data['timestamp'] = self.timestamp.isoformat()
123
+ return data
@@ -0,0 +1,5 @@
1
+ """Utils package"""
2
+
3
+ from .whitelist import WhitelistManager
4
+
5
+ __all__ = ['WhitelistManager']
@@ -0,0 +1,124 @@
1
+ """
2
+ Punishment executor for anti-nuke violations.
3
+ Handles kick, ban, timeout, and warn actions.
4
+ """
5
+ import discord
6
+ from datetime import timedelta
7
+ from typing import Optional
8
+
9
+
10
+ class PunishmentExecutor:
11
+ """Executes punishment actions on violators"""
12
+
13
+ def __init__(self, bot):
14
+ """Initialize with bot instance"""
15
+ self.bot = bot
16
+
17
+ async def punish(
18
+ self,
19
+ guild: discord.Guild,
20
+ user: discord.User,
21
+ violation_type: str,
22
+ details: str = None,
23
+ sdk = None
24
+ ) -> str:
25
+ """
26
+ Execute punishment on violator based on violation type.
27
+
28
+ Args:
29
+ guild: The guild where violation occurred
30
+ user: The user who committed the violation
31
+ violation_type: Type of violation (e.g., "channel_delete")
32
+ details: Optional details about the violation
33
+ sdk: SDK instance for accessing punishment config
34
+
35
+ Returns:
36
+ The punishment action that was applied ("none", "warn", "timeout", "kick", "ban")
37
+ """
38
+ if sdk is None:
39
+ return "none"
40
+
41
+
42
+ action = sdk.punishments.get(violation_type, "none")
43
+
44
+ if action == "none":
45
+ return action
46
+
47
+ member = guild.get_member(user.id)
48
+ if not member:
49
+ print(f"Cannot punish {user}: not a member")
50
+ return action
51
+
52
+
53
+ if member.id == guild.owner_id:
54
+ print(f"Cannot punish server owner {user}")
55
+ return action
56
+
57
+
58
+
59
+ try:
60
+ if action == "warn":
61
+
62
+ print(f"⚠️ Warned {user} for {violation_type}")
63
+
64
+ elif action == "timeout":
65
+ duration = timedelta(seconds=sdk.timeout_duration)
66
+ await member.timeout(duration, reason=f"SecureX: {violation_type}")
67
+ print(f"⏱️ Timed out {user} for {sdk.timeout_duration}s ({violation_type})")
68
+
69
+ elif action == "kick":
70
+ await member.kick(reason=f"SecureX: {violation_type}")
71
+ print(f"👢 Kicked {user} for {violation_type}")
72
+
73
+ elif action == "ban":
74
+ await member.ban(reason=f"SecureX: {violation_type}", delete_message_days=0)
75
+ print(f"🔨 Banned {user} for {violation_type}")
76
+
77
+ if sdk.notify_punished_user:
78
+ await self._notify_user(member, violation_type, action, details, sdk)
79
+
80
+
81
+ except discord.Forbidden:
82
+ print(f"❌ Missing permissions to {action} {user}")
83
+ except Exception as e:
84
+ print(f"Error punishing {user}: {e}")
85
+
86
+ return action
87
+
88
+ async def _notify_user(
89
+ self,
90
+ member: discord.Member,
91
+ violation_type: str,
92
+ action: str,
93
+ details: Optional[str],
94
+ sdk
95
+ ):
96
+ """Send DM notification to punished user"""
97
+ try:
98
+
99
+ readable_type = violation_type.replace("_", " ").title()
100
+
101
+ embed = discord.Embed(
102
+ title="🚨 Anti-Nuke Violation Detected",
103
+ description=f"You triggered anti-nuke protection in **{member.guild.name}**",
104
+ color=discord.Color.red()
105
+ )
106
+ embed.add_field(name="Violation Type", value=readable_type, inline=False)
107
+ if details:
108
+ embed.add_field(name="Details", value=details, inline=False)
109
+ embed.add_field(
110
+ name="Action Taken",
111
+ value=f"**{action.title()}**",
112
+ inline=False
113
+ )
114
+
115
+ if action == "timeout":
116
+ mins = sdk.timeout_duration // 60
117
+ embed.add_field(name="Duration", value=f"{mins} minutes", inline=False)
118
+
119
+ await member.send(embed=embed)
120
+ print(f"📨 Sent punishment notification to {member}")
121
+ except discord.Forbidden:
122
+ print(f"Cannot DM {member} (DMs disabled)")
123
+ except Exception as e:
124
+ print(f"Error notifying {member}: {e}")
@@ -0,0 +1,129 @@
1
+ """
2
+ Whitelist management for SecureX SDK.
3
+ Strictly per-guild - no cross-server whitelisting.
4
+ """
5
+ import json
6
+ import aiofiles
7
+ from pathlib import Path
8
+ from typing import List, Set
9
+ from ..models import WhitelistChange
10
+
11
+
12
+ class WhitelistManager:
13
+ """Manages whitelisted users per guild (guild-specific only)"""
14
+
15
+ def __init__(self, sdk):
16
+ self.sdk = sdk
17
+ self.data_dir = Path("./data/whitelists")
18
+ self.data_dir.mkdir(parents=True, exist_ok=True)
19
+
20
+
21
+ self._whitelists: dict[int, Set[int]] = {}
22
+ self._cache_loaded = False
23
+
24
+ def _get_file_path(self, guild_id: int) -> Path:
25
+ """Get whitelist file path for guild"""
26
+ return self.data_dir / f"{guild_id}.json"
27
+
28
+ async def preload_all(self):
29
+ """
30
+ Preload ALL whitelists into memory on startup.
31
+ Call this in enable() to warm the cache.
32
+ """
33
+ if self._cache_loaded:
34
+ return
35
+
36
+ print("🔄 Preloading whitelists into cache...")
37
+ count = 0
38
+
39
+
40
+ for file_path in self.data_dir.glob("*.json"):
41
+ try:
42
+ guild_id = int(file_path.stem)
43
+ async with aiofiles.open(file_path, 'r') as f:
44
+ content = await f.read()
45
+ data = json.loads(content)
46
+ self._whitelists[guild_id] = set(data.get('users', []))
47
+ count += 1
48
+ except Exception as e:
49
+ print(f"Error loading whitelist {file_path}: {e}")
50
+
51
+ self._cache_loaded = True
52
+ print(f"✅ Cached {count} whitelists in memory")
53
+
54
+ async def _load_whitelist(self, guild_id: int):
55
+ """Load whitelist from file (fallback if not preloaded)"""
56
+ file_path = self._get_file_path(guild_id)
57
+ if file_path.exists():
58
+ async with aiofiles.open(file_path, 'r') as f:
59
+ content = await f.read()
60
+ data = json.loads(content)
61
+ self._whitelists[guild_id] = set(data.get('users', []))
62
+
63
+ async def _save_whitelist(self, guild_id: int):
64
+ """Save whitelist to file (async)"""
65
+ file_path = self._get_file_path(guild_id)
66
+ async with aiofiles.open(file_path, 'w') as f:
67
+ content = json.dumps({
68
+ 'users': list(self._whitelists.get(guild_id, set()))
69
+ }, indent=2)
70
+ await f.write(content)
71
+
72
+ async def add(self, guild_id: int, user_id: int, moderator_id: int = None):
73
+ """Add user to guild whitelist"""
74
+ if guild_id not in self._whitelists:
75
+ await self._load_whitelist(guild_id)
76
+ if guild_id not in self._whitelists:
77
+ self._whitelists[guild_id] = set()
78
+
79
+ self._whitelists[guild_id].add(user_id)
80
+ await self._save_whitelist(guild_id)
81
+
82
+
83
+ change = WhitelistChange(
84
+ guild_id=guild_id,
85
+ user_id=user_id,
86
+ action="added",
87
+ moderator_id=moderator_id
88
+ )
89
+ await self.sdk._trigger_callbacks('whitelist_changed', change)
90
+
91
+ async def remove(self, guild_id: int, user_id: int, moderator_id: int = None):
92
+ """Remove user from guild whitelist"""
93
+ if guild_id not in self._whitelists:
94
+ await self._load_whitelist(guild_id)
95
+
96
+ if guild_id in self._whitelists:
97
+ self._whitelists[guild_id].discard(user_id)
98
+ await self._save_whitelist(guild_id)
99
+
100
+
101
+ change = WhitelistChange(
102
+ guild_id=guild_id,
103
+ user_id=user_id,
104
+ action="removed",
105
+ moderator_id=moderator_id
106
+ )
107
+ await self.sdk._trigger_callbacks('whitelist_changed', change)
108
+
109
+ async def get_all(self, guild_id: int) -> List[int]:
110
+ """Get all whitelisted users for guild"""
111
+ if guild_id not in self._whitelists:
112
+ await self._load_whitelist(guild_id)
113
+ return list(self._whitelists.get(guild_id, set()))
114
+
115
+ async def is_whitelisted(self, guild_id: int, user_id: int) -> bool:
116
+ """
117
+ Check if user is whitelisted in specific guild.
118
+ Uses cached data - instant response (no file I/O).
119
+ """
120
+ if guild_id not in self._whitelists:
121
+ await self._load_whitelist(guild_id)
122
+
123
+ return user_id in self._whitelists.get(guild_id, set())
124
+
125
+ async def clear(self, guild_id: int):
126
+ """Clear all whitelisted users for a guild"""
127
+ self._whitelists[guild_id] = set()
128
+ await self._save_whitelist(guild_id)
129
+