dc-securex 2.29.7__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.
- dc_securex-2.29.7.dist-info/METADATA +1028 -0
- dc_securex-2.29.7.dist-info/RECORD +28 -0
- dc_securex-2.29.7.dist-info/WHEEL +5 -0
- dc_securex-2.29.7.dist-info/licenses/LICENSE +21 -0
- dc_securex-2.29.7.dist-info/top_level.txt +2 -0
- securex/__init__.py +18 -0
- securex/backup/__init__.py +5 -0
- securex/backup/manager.py +792 -0
- securex/client.py +329 -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 +156 -0
- securex/utils/__init__.py +5 -0
- securex/utils/punishment.py +124 -0
- securex/utils/whitelist.py +129 -0
- securex/workers/__init__.py +8 -0
- securex/workers/action_worker.py +115 -0
- securex/workers/cleanup_worker.py +94 -0
- securex/workers/guild_worker.py +186 -0
- securex/workers/log_worker.py +88 -0
- tests/__init__.py +9 -0
- tests/test_coverage_client.py +210 -0
- tests/test_coverage_handlers.py +341 -0
- tests/test_coverage_manager.py +504 -0
- tests/test_coverage_utils.py +177 -0
- tests/test_coverage_workers.py +297 -0
securex/models.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
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, timezone
|
|
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=lambda: datetime.now(timezone.utc))
|
|
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=lambda: datetime.now(timezone.utc))
|
|
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
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class UserToken:
|
|
128
|
+
"""
|
|
129
|
+
Represents a user token for guild-level API operations.
|
|
130
|
+
|
|
131
|
+
Attributes:
|
|
132
|
+
guild_id: Guild this token is associated with
|
|
133
|
+
token: The user authentication token
|
|
134
|
+
set_by: User ID who set this token
|
|
135
|
+
timestamp: When the token was added/updated
|
|
136
|
+
last_used: When the token was last used (optional)
|
|
137
|
+
description: Optional description/note about the token
|
|
138
|
+
"""
|
|
139
|
+
guild_id: int
|
|
140
|
+
token: str
|
|
141
|
+
set_by: Optional[int] = None
|
|
142
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
143
|
+
last_used: Optional[datetime] = None
|
|
144
|
+
description: Optional[str] = None
|
|
145
|
+
|
|
146
|
+
def to_dict(self) -> dict:
|
|
147
|
+
"""Convert to dictionary"""
|
|
148
|
+
data = asdict(self)
|
|
149
|
+
data['timestamp'] = self.timestamp.isoformat()
|
|
150
|
+
if self.last_used:
|
|
151
|
+
data['last_used'] = self.last_used.isoformat()
|
|
152
|
+
return data
|
|
153
|
+
|
|
154
|
+
def mark_used(self):
|
|
155
|
+
"""Mark this token as used (updates last_used timestamp)"""
|
|
156
|
+
self.last_used = datetime.now(timezone.utc)
|
|
@@ -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
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Workers package - background processing workers"""
|
|
2
|
+
|
|
3
|
+
from .action_worker import ActionWorker
|
|
4
|
+
from .cleanup_worker import CleanupWorker
|
|
5
|
+
from .log_worker import LogWorker
|
|
6
|
+
from .guild_worker import GuildWorker
|
|
7
|
+
|
|
8
|
+
__all__ = ['ActionWorker', 'CleanupWorker', 'LogWorker', 'GuildWorker']
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Action Worker - Instant punishment for unauthorized actions
|
|
3
|
+
"""
|
|
4
|
+
import discord
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ActionWorker:
|
|
9
|
+
"""Worker that bans/punishes violators instantly"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, sdk):
|
|
12
|
+
self.sdk = sdk
|
|
13
|
+
self.bot = sdk.bot
|
|
14
|
+
self.action_queue = asyncio.Queue()
|
|
15
|
+
self._worker_task = None
|
|
16
|
+
|
|
17
|
+
self.action_map = {
|
|
18
|
+
discord.AuditLogAction.bot_add: "bot_add",
|
|
19
|
+
discord.AuditLogAction.channel_create: "channel_create",
|
|
20
|
+
discord.AuditLogAction.channel_delete: "channel_delete",
|
|
21
|
+
discord.AuditLogAction.channel_update: "channel_update",
|
|
22
|
+
discord.AuditLogAction.role_create: "role_create",
|
|
23
|
+
discord.AuditLogAction.role_delete: "role_delete",
|
|
24
|
+
discord.AuditLogAction.role_update: "role_update",
|
|
25
|
+
discord.AuditLogAction.kick: "member_kick",
|
|
26
|
+
discord.AuditLogAction.ban: "member_ban",
|
|
27
|
+
discord.AuditLogAction.unban: "member_unban",
|
|
28
|
+
discord.AuditLogAction.member_update: "member_update",
|
|
29
|
+
discord.AuditLogAction.webhook_create: "webhook_create",
|
|
30
|
+
discord.AuditLogAction.guild_update: "guild_update",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async def start(self):
|
|
34
|
+
"""Start the action worker"""
|
|
35
|
+
if self._worker_task is None or self._worker_task.done():
|
|
36
|
+
self._worker_task = asyncio.create_task(self._worker_loop())
|
|
37
|
+
print("โก Action Worker started")
|
|
38
|
+
|
|
39
|
+
async def stop(self):
|
|
40
|
+
"""Stop the worker"""
|
|
41
|
+
if self._worker_task:
|
|
42
|
+
self._worker_task.cancel()
|
|
43
|
+
self._worker_task = None
|
|
44
|
+
|
|
45
|
+
async def _worker_loop(self):
|
|
46
|
+
"""Main worker loop"""
|
|
47
|
+
while True:
|
|
48
|
+
try:
|
|
49
|
+
entry = await self.action_queue.get()
|
|
50
|
+
await self._process_entry(entry)
|
|
51
|
+
except asyncio.CancelledError:
|
|
52
|
+
break
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(f"Error in action worker: {e}")
|
|
55
|
+
|
|
56
|
+
async def _process_entry(self, entry):
|
|
57
|
+
"""Process audit log entry"""
|
|
58
|
+
print(f"๐ [ActionWorker] Processing: {entry.action} by {entry.user.name}")
|
|
59
|
+
try:
|
|
60
|
+
guild = entry.guild
|
|
61
|
+
executor = entry.user
|
|
62
|
+
|
|
63
|
+
if executor.id == self.bot.user.id:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
if executor.id == guild.owner_id:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
whitelist_set = self.sdk.whitelist_cache.get(guild.id, set())
|
|
70
|
+
if executor.id in whitelist_set:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
guild_punishments = self.sdk.punishment_cache.get(guild.id, self.sdk.punishments)
|
|
74
|
+
|
|
75
|
+
violation_type = self.action_map.get(entry.action)
|
|
76
|
+
if not violation_type:
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
punishment_action = guild_punishments.get(violation_type, "none")
|
|
80
|
+
punishment_result = "none"
|
|
81
|
+
|
|
82
|
+
if violation_type == "bot_add":
|
|
83
|
+
try:
|
|
84
|
+
bot_member = guild.get_member(entry.target.id)
|
|
85
|
+
if bot_member:
|
|
86
|
+
await bot_member.ban(reason="SecureX: Unauthorized bot addition")
|
|
87
|
+
punishment_result = "banned_bot"
|
|
88
|
+
print(f"๐ INSTANT: Banned bot {entry.target.name} (added by {executor.name})")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"Error banning bot: {e}")
|
|
91
|
+
|
|
92
|
+
if violation_type in ["member_kick", "member_ban", "member_unban", "webhook_create", "guild_update", "member_update"]:
|
|
93
|
+
try:
|
|
94
|
+
await guild.ban(executor, reason=f"SecureX: Unauthorized {violation_type}")
|
|
95
|
+
punishment_result = "banned_violator"
|
|
96
|
+
print(f"๐จ INSTANT: Banned {executor.name} for {violation_type}")
|
|
97
|
+
except Exception as e:
|
|
98
|
+
print(f"Error banning violator: {e}")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if punishment_action != "none":
|
|
102
|
+
try:
|
|
103
|
+
punishment_result = await self.sdk.punisher.punish(
|
|
104
|
+
guild=guild,
|
|
105
|
+
user=executor,
|
|
106
|
+
violation_type=violation_type,
|
|
107
|
+
details=f"{violation_type} on {getattr(entry.target, 'name', 'Unknown')}",
|
|
108
|
+
sdk=self.sdk
|
|
109
|
+
)
|
|
110
|
+
print(f"๐ข {punishment_result.title()} {executor.name} for {violation_type}")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(f"Cannot punish {executor.name}: {e}")
|
|
113
|
+
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print(f"Error processing action entry: {e}")
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cleanup Worker - Deletes unauthorized creations instantly
|
|
3
|
+
"""
|
|
4
|
+
import discord
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CleanupWorker:
|
|
9
|
+
"""Worker that deletes unauthorized creations (channels, roles, webhooks)"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, sdk):
|
|
12
|
+
self.sdk = sdk
|
|
13
|
+
self.bot = sdk.bot
|
|
14
|
+
self.cleanup_queue = asyncio.Queue()
|
|
15
|
+
self._worker_task = None
|
|
16
|
+
|
|
17
|
+
async def start(self):
|
|
18
|
+
"""Start the cleanup worker"""
|
|
19
|
+
if self._worker_task is None or self._worker_task.done():
|
|
20
|
+
self._worker_task = asyncio.create_task(self._worker_loop())
|
|
21
|
+
print("๐งน Cleanup Worker started")
|
|
22
|
+
|
|
23
|
+
async def stop(self):
|
|
24
|
+
"""Stop the worker"""
|
|
25
|
+
if self._worker_task:
|
|
26
|
+
self._worker_task.cancel()
|
|
27
|
+
self._worker_task = None
|
|
28
|
+
|
|
29
|
+
async def _worker_loop(self):
|
|
30
|
+
"""Main worker loop"""
|
|
31
|
+
while True:
|
|
32
|
+
try:
|
|
33
|
+
entry = await self.cleanup_queue.get()
|
|
34
|
+
await self._process_entry(entry)
|
|
35
|
+
except asyncio.CancelledError:
|
|
36
|
+
break
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"Error in cleanup worker: {e}")
|
|
39
|
+
|
|
40
|
+
async def _process_entry(self, entry):
|
|
41
|
+
"""Process deletion of unauthorized creations"""
|
|
42
|
+
try:
|
|
43
|
+
guild = entry.guild
|
|
44
|
+
executor = entry.user
|
|
45
|
+
|
|
46
|
+
if executor.id == self.bot.user.id:
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if executor.id == guild.owner_id:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
whitelist_set = self.sdk.whitelist_cache.get(guild.id, set())
|
|
53
|
+
if executor.id in whitelist_set:
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
if entry.action not in [
|
|
57
|
+
discord.AuditLogAction.channel_create,
|
|
58
|
+
discord.AuditLogAction.role_create,
|
|
59
|
+
discord.AuditLogAction.webhook_create
|
|
60
|
+
]:
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
target = entry.target
|
|
65
|
+
if not target:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
if entry.action == discord.AuditLogAction.role_create:
|
|
69
|
+
role = guild.get_role(target.id)
|
|
70
|
+
if role:
|
|
71
|
+
await role.delete(reason="SecureX: Unauthorized role creation")
|
|
72
|
+
print(f"๐๏ธ [Worker] Deleted unauthorized role: {role.name}")
|
|
73
|
+
|
|
74
|
+
elif entry.action == discord.AuditLogAction.channel_create:
|
|
75
|
+
channel = guild.get_channel(target.id)
|
|
76
|
+
if channel:
|
|
77
|
+
await channel.delete(reason="SecureX: Unauthorized channel creation")
|
|
78
|
+
print(f"๐๏ธ [Worker] Deleted unauthorized channel: {channel.name}")
|
|
79
|
+
|
|
80
|
+
elif entry.action == discord.AuditLogAction.webhook_create:
|
|
81
|
+
webhooks = await guild.webhooks()
|
|
82
|
+
for webhook in webhooks:
|
|
83
|
+
if webhook.id == target.id:
|
|
84
|
+
await webhook.delete(reason="SecureX: Unauthorized webhook creation")
|
|
85
|
+
print(f"๐๏ธ [Worker] Deleted unauthorized webhook: {webhook.name}")
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
except (discord.Forbidden, discord.NotFound):
|
|
89
|
+
pass
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f"Cleanup error: {e}")
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f"Error processing cleanup entry: {e}")
|