ManagerX-DevTools 1.0.0__tar.gz
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.
- managerx_devtools-1.0.0/DevTools/__init__.py +1 -0
- managerx_devtools-1.0.0/DevTools/backend/__init__.py +1 -0
- managerx_devtools-1.0.0/DevTools/backend/database/__init__.py +12 -0
- managerx_devtools-1.0.0/DevTools/backend/database/antispam_db.py +494 -0
- managerx_devtools-1.0.0/DevTools/backend/database/autodelete_db.py +843 -0
- managerx_devtools-1.0.0/DevTools/backend/database/autorole_db.py +101 -0
- managerx_devtools-1.0.0/DevTools/backend/database/globalchat_db.py +955 -0
- managerx_devtools-1.0.0/DevTools/backend/database/levelsystem_db.py +758 -0
- managerx_devtools-1.0.0/DevTools/backend/database/logging_db.py +413 -0
- managerx_devtools-1.0.0/DevTools/backend/database/notes_db.py +55 -0
- managerx_devtools-1.0.0/DevTools/backend/database/settings_db.py +46 -0
- managerx_devtools-1.0.0/DevTools/backend/database/stats_db.py +476 -0
- managerx_devtools-1.0.0/DevTools/backend/database/vc_db.py +176 -0
- managerx_devtools-1.0.0/DevTools/backend/database/warn_db.py +121 -0
- managerx_devtools-1.0.0/DevTools/backend/database/welcome_db.py +552 -0
- managerx_devtools-1.0.0/LICENSE +675 -0
- managerx_devtools-1.0.0/ManagerX_DevTools.egg-info/PKG-INFO +32 -0
- managerx_devtools-1.0.0/ManagerX_DevTools.egg-info/SOURCES.txt +23 -0
- managerx_devtools-1.0.0/ManagerX_DevTools.egg-info/dependency_links.txt +1 -0
- managerx_devtools-1.0.0/ManagerX_DevTools.egg-info/requires.txt +6 -0
- managerx_devtools-1.0.0/ManagerX_DevTools.egg-info/top_level.txt +1 -0
- managerx_devtools-1.0.0/PKG-INFO +32 -0
- managerx_devtools-1.0.0/README.md +1 -0
- managerx_devtools-1.0.0/pyproject.toml +42 -0
- managerx_devtools-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .backend import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .database import *
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .antispam_db import SpamDB as AntiSpamDatabase
|
|
2
|
+
from .autodelete_db import AutoDeleteDB
|
|
3
|
+
from .autorole_db import AutoRoleDatabase
|
|
4
|
+
from .globalchat_db import GlobalChatDatabase
|
|
5
|
+
from .settings_db import SettingsDB
|
|
6
|
+
from .levelsystem_db import LevelDatabase
|
|
7
|
+
from .logging_db import LoggingDatabase
|
|
8
|
+
from .notes_db import NotesDatabase
|
|
9
|
+
from .stats_db import StatsDB
|
|
10
|
+
from .vc_db import TempVCDatabase
|
|
11
|
+
from .warn_db import WarnDatabase
|
|
12
|
+
from .welcome_db import WelcomeDatabase
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
# Copyright (c) 2025 OPPRO.NET Network
|
|
2
|
+
import sqlite3
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
from typing import Optional, Dict, List, Tuple
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
|
|
9
|
+
from colorama import Fore, Style
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SpamDBError(Exception):
|
|
13
|
+
"""Custom exception for SpamDB errors"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SpamDB:
|
|
18
|
+
def __init__(self, db_path='data/spam.db'):
|
|
19
|
+
"""Initialize spam database with enhanced error handling and logging."""
|
|
20
|
+
self.db_path = db_path
|
|
21
|
+
self.logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
# Ensure data directory exists
|
|
25
|
+
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
|
26
|
+
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
27
|
+
self.conn.row_factory = sqlite3.Row # Enable dict-like access
|
|
28
|
+
self.create_tables()
|
|
29
|
+
self._migrate_database() # Add migration step
|
|
30
|
+
self._init_database()
|
|
31
|
+
except Exception as e:
|
|
32
|
+
self.logger.error(f"Failed to initialize database: {e}")
|
|
33
|
+
raise SpamDBError(f"Database initialization failed: {e}")
|
|
34
|
+
|
|
35
|
+
@contextmanager
|
|
36
|
+
def get_cursor(self):
|
|
37
|
+
"""Context manager for database operations with proper error handling."""
|
|
38
|
+
cursor = self.conn.cursor()
|
|
39
|
+
try:
|
|
40
|
+
yield cursor
|
|
41
|
+
except sqlite3.Error as e:
|
|
42
|
+
self.conn.rollback()
|
|
43
|
+
self.logger.error(f"Database error: {e}")
|
|
44
|
+
raise SpamDBError(f"Database operation failed: {e}")
|
|
45
|
+
finally:
|
|
46
|
+
cursor.close()
|
|
47
|
+
|
|
48
|
+
def _get_table_columns(self, table_name: str) -> List[str]:
|
|
49
|
+
"""Get list of columns for a table."""
|
|
50
|
+
with self.get_cursor() as cursor:
|
|
51
|
+
cursor.execute(f"PRAGMA table_info({table_name})")
|
|
52
|
+
return [row[1] for row in cursor.fetchall()]
|
|
53
|
+
|
|
54
|
+
def _migrate_database(self):
|
|
55
|
+
"""Handle database migrations for schema changes."""
|
|
56
|
+
try:
|
|
57
|
+
tables = [table[0] for table in self._get_tables()]
|
|
58
|
+
|
|
59
|
+
# Migrate spam_settings table
|
|
60
|
+
if 'spam_settings' in tables:
|
|
61
|
+
spam_settings_columns = self._get_table_columns('spam_settings')
|
|
62
|
+
if 'created_at' not in spam_settings_columns:
|
|
63
|
+
with self.get_cursor() as cursor:
|
|
64
|
+
cursor.execute(
|
|
65
|
+
'ALTER TABLE spam_settings ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP')
|
|
66
|
+
|
|
67
|
+
if 'updated_at' not in spam_settings_columns:
|
|
68
|
+
with self.get_cursor() as cursor:
|
|
69
|
+
cursor.execute(
|
|
70
|
+
'ALTER TABLE spam_settings ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP')
|
|
71
|
+
|
|
72
|
+
# Migrate spam_logs table
|
|
73
|
+
if 'spam_logs' in tables:
|
|
74
|
+
spam_logs_columns = self._get_table_columns('spam_logs')
|
|
75
|
+
if 'message_count' not in spam_logs_columns:
|
|
76
|
+
with self.get_cursor() as cursor:
|
|
77
|
+
cursor.execute('ALTER TABLE spam_logs ADD COLUMN message_count INTEGER DEFAULT 1')
|
|
78
|
+
|
|
79
|
+
# Migrate spam_whitelist table
|
|
80
|
+
if 'spam_whitelist' in tables:
|
|
81
|
+
whitelist_columns = self._get_table_columns('spam_whitelist')
|
|
82
|
+
if 'added_by' not in whitelist_columns:
|
|
83
|
+
with self.get_cursor() as cursor:
|
|
84
|
+
cursor.execute('ALTER TABLE spam_whitelist ADD COLUMN added_by INTEGER')
|
|
85
|
+
|
|
86
|
+
if 'added_at' not in whitelist_columns:
|
|
87
|
+
with self.get_cursor() as cursor:
|
|
88
|
+
cursor.execute(
|
|
89
|
+
'ALTER TABLE spam_whitelist ADD COLUMN added_at DATETIME DEFAULT CURRENT_TIMESTAMP')
|
|
90
|
+
|
|
91
|
+
if 'reason' not in whitelist_columns:
|
|
92
|
+
with self.get_cursor() as cursor:
|
|
93
|
+
cursor.execute('ALTER TABLE spam_whitelist ADD COLUMN reason TEXT')
|
|
94
|
+
|
|
95
|
+
self.conn.commit()
|
|
96
|
+
self.logger.info("Database migration completed successfully")
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
self.logger.error(f"Database migration failed: {e}")
|
|
100
|
+
# Don't raise here - let the application continue with basic functionality
|
|
101
|
+
|
|
102
|
+
def _get_tables(self) -> List[Tuple]:
|
|
103
|
+
"""Get list of all tables in the database."""
|
|
104
|
+
with self.get_cursor() as cursor:
|
|
105
|
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
|
106
|
+
return cursor.fetchall()
|
|
107
|
+
|
|
108
|
+
def create_tables(self):
|
|
109
|
+
"""Create all necessary tables with improved schema."""
|
|
110
|
+
with self.get_cursor() as cursor:
|
|
111
|
+
# Spam settings table with better constraints
|
|
112
|
+
cursor.execute('''
|
|
113
|
+
CREATE TABLE IF NOT EXISTS spam_settings (
|
|
114
|
+
guild_id INTEGER PRIMARY KEY,
|
|
115
|
+
max_messages INTEGER DEFAULT 5 CHECK (max_messages > 0),
|
|
116
|
+
time_frame INTEGER DEFAULT 10 CHECK (time_frame > 0),
|
|
117
|
+
log_channel_id INTEGER
|
|
118
|
+
)
|
|
119
|
+
''')
|
|
120
|
+
|
|
121
|
+
# Spam logs with better indexing
|
|
122
|
+
cursor.execute('''
|
|
123
|
+
CREATE TABLE IF NOT EXISTS spam_logs (
|
|
124
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
125
|
+
guild_id INTEGER NOT NULL,
|
|
126
|
+
user_id INTEGER NOT NULL,
|
|
127
|
+
message TEXT NOT NULL,
|
|
128
|
+
message_count INTEGER DEFAULT 1,
|
|
129
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
130
|
+
FOREIGN KEY (guild_id) REFERENCES spam_settings(guild_id)
|
|
131
|
+
)
|
|
132
|
+
''')
|
|
133
|
+
|
|
134
|
+
# Whitelist with better constraints
|
|
135
|
+
cursor.execute('''
|
|
136
|
+
CREATE TABLE IF NOT EXISTS spam_whitelist (
|
|
137
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
138
|
+
guild_id INTEGER NOT NULL,
|
|
139
|
+
user_id INTEGER NOT NULL,
|
|
140
|
+
UNIQUE(guild_id, user_id)
|
|
141
|
+
)
|
|
142
|
+
''')
|
|
143
|
+
|
|
144
|
+
# Create indexes for better performance
|
|
145
|
+
cursor.execute('''
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_spam_logs_guild_timestamp
|
|
147
|
+
ON spam_logs(guild_id, timestamp)
|
|
148
|
+
''')
|
|
149
|
+
|
|
150
|
+
cursor.execute('''
|
|
151
|
+
CREATE INDEX IF NOT EXISTS idx_spam_logs_user_timestamp
|
|
152
|
+
ON spam_logs(user_id, timestamp)
|
|
153
|
+
''')
|
|
154
|
+
|
|
155
|
+
cursor.execute('''
|
|
156
|
+
CREATE INDEX IF NOT EXISTS idx_whitelist_guild_user
|
|
157
|
+
ON spam_whitelist(guild_id, user_id)
|
|
158
|
+
''')
|
|
159
|
+
|
|
160
|
+
self.conn.commit()
|
|
161
|
+
|
|
162
|
+
def _init_database(self):
|
|
163
|
+
"""Initialize database with any required default data."""
|
|
164
|
+
# Add any initialization logic here if needed
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
def set_spam_settings(self, guild_id: int, max_messages: int = 5,
|
|
168
|
+
time_frame: int = 10, log_channel_id: Optional[int] = None) -> bool:
|
|
169
|
+
"""Set spam detection settings for a guild with validation."""
|
|
170
|
+
if max_messages <= 0 or time_frame <= 0:
|
|
171
|
+
raise SpamDBError("max_messages and time_frame must be positive integers")
|
|
172
|
+
|
|
173
|
+
with self.get_cursor() as cursor:
|
|
174
|
+
# Check if updated_at column exists
|
|
175
|
+
columns = self._get_table_columns('spam_settings')
|
|
176
|
+
|
|
177
|
+
if 'updated_at' in columns:
|
|
178
|
+
cursor.execute('''
|
|
179
|
+
INSERT OR REPLACE INTO spam_settings
|
|
180
|
+
(guild_id, max_messages, time_frame, log_channel_id, updated_at)
|
|
181
|
+
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
182
|
+
''', (guild_id, max_messages, time_frame, log_channel_id))
|
|
183
|
+
else:
|
|
184
|
+
# Fallback for tables without updated_at column
|
|
185
|
+
cursor.execute('''
|
|
186
|
+
INSERT OR REPLACE INTO spam_settings
|
|
187
|
+
(guild_id, max_messages, time_frame, log_channel_id)
|
|
188
|
+
VALUES (?, ?, ?, ?)
|
|
189
|
+
''', (guild_id, max_messages, time_frame, log_channel_id))
|
|
190
|
+
|
|
191
|
+
self.conn.commit()
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
def set_log_channel(self, guild_id: int, channel_id: int) -> bool:
|
|
195
|
+
"""Set the log channel for a guild."""
|
|
196
|
+
with self.get_cursor() as cursor:
|
|
197
|
+
# Get current settings or use defaults
|
|
198
|
+
cursor.execute('SELECT max_messages, time_frame FROM spam_settings WHERE guild_id = ?',
|
|
199
|
+
(guild_id,))
|
|
200
|
+
result = cursor.fetchone()
|
|
201
|
+
|
|
202
|
+
if result:
|
|
203
|
+
max_messages, time_frame = result['max_messages'], result['time_frame']
|
|
204
|
+
else:
|
|
205
|
+
max_messages, time_frame = 5, 10 # Default values
|
|
206
|
+
|
|
207
|
+
# Check if updated_at column exists
|
|
208
|
+
columns = self._get_table_columns('spam_settings')
|
|
209
|
+
|
|
210
|
+
if 'updated_at' in columns:
|
|
211
|
+
cursor.execute('''
|
|
212
|
+
INSERT OR REPLACE INTO spam_settings
|
|
213
|
+
(guild_id, max_messages, time_frame, log_channel_id, updated_at)
|
|
214
|
+
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
215
|
+
''', (guild_id, max_messages, time_frame, channel_id))
|
|
216
|
+
else:
|
|
217
|
+
cursor.execute('''
|
|
218
|
+
INSERT OR REPLACE INTO spam_settings
|
|
219
|
+
(guild_id, max_messages, time_frame, log_channel_id)
|
|
220
|
+
VALUES (?, ?, ?, ?)
|
|
221
|
+
''', (guild_id, max_messages, time_frame, channel_id))
|
|
222
|
+
|
|
223
|
+
self.conn.commit()
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
def get_spam_settings(self, guild_id: int) -> Optional[Dict]:
|
|
227
|
+
"""Get spam settings for a guild."""
|
|
228
|
+
with self.get_cursor() as cursor:
|
|
229
|
+
columns = self._get_table_columns('spam_settings')
|
|
230
|
+
|
|
231
|
+
# Build query based on available columns
|
|
232
|
+
select_columns = ['max_messages', 'time_frame', 'log_channel_id']
|
|
233
|
+
if 'created_at' in columns:
|
|
234
|
+
select_columns.append('created_at')
|
|
235
|
+
if 'updated_at' in columns:
|
|
236
|
+
select_columns.append('updated_at')
|
|
237
|
+
|
|
238
|
+
query = f"SELECT {', '.join(select_columns)} FROM spam_settings WHERE guild_id = ?"
|
|
239
|
+
cursor.execute(query, (guild_id,))
|
|
240
|
+
result = cursor.fetchone()
|
|
241
|
+
|
|
242
|
+
if result:
|
|
243
|
+
settings = {
|
|
244
|
+
'max_messages': result['max_messages'],
|
|
245
|
+
'time_frame': result['time_frame'],
|
|
246
|
+
'log_channel_id': result['log_channel_id']
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if 'created_at' in columns:
|
|
250
|
+
settings['created_at'] = result.get('created_at')
|
|
251
|
+
if 'updated_at' in columns:
|
|
252
|
+
settings['updated_at'] = result.get('updated_at')
|
|
253
|
+
|
|
254
|
+
return settings
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
def get_log_channel(self, guild_id: int) -> Optional[int]:
|
|
258
|
+
"""Get the log channel ID for a guild."""
|
|
259
|
+
with self.get_cursor() as cursor:
|
|
260
|
+
cursor.execute('''
|
|
261
|
+
SELECT log_channel_id FROM spam_settings WHERE guild_id = ?
|
|
262
|
+
''', (guild_id,))
|
|
263
|
+
result = cursor.fetchone()
|
|
264
|
+
return result['log_channel_id'] if result and result['log_channel_id'] else None
|
|
265
|
+
|
|
266
|
+
def log_spam(self, guild_id: int, user_id: int, message: str, message_count: int = 1) -> bool:
|
|
267
|
+
"""Log a spam incident with message count."""
|
|
268
|
+
with self.get_cursor() as cursor:
|
|
269
|
+
columns = self._get_table_columns('spam_logs')
|
|
270
|
+
|
|
271
|
+
if 'message_count' in columns:
|
|
272
|
+
cursor.execute('''
|
|
273
|
+
INSERT INTO spam_logs (guild_id, user_id, message, message_count)
|
|
274
|
+
VALUES (?, ?, ?, ?)
|
|
275
|
+
''', (guild_id, user_id, message[:1000], message_count))
|
|
276
|
+
else:
|
|
277
|
+
# Fallback for tables without message_count column
|
|
278
|
+
cursor.execute('''
|
|
279
|
+
INSERT INTO spam_logs (guild_id, user_id, message)
|
|
280
|
+
VALUES (?, ?, ?)
|
|
281
|
+
''', (guild_id, user_id, message[:1000]))
|
|
282
|
+
|
|
283
|
+
self.conn.commit()
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
def get_spam_logs(self, guild_id: int, limit: int = 10) -> List[Dict]:
|
|
287
|
+
"""Get recent spam logs for a guild."""
|
|
288
|
+
with self.get_cursor() as cursor:
|
|
289
|
+
cursor.execute('''
|
|
290
|
+
SELECT user_id, message, message_count, timestamp
|
|
291
|
+
FROM spam_logs WHERE guild_id = ?
|
|
292
|
+
ORDER BY timestamp DESC LIMIT ?
|
|
293
|
+
''', (guild_id, limit))
|
|
294
|
+
|
|
295
|
+
return [
|
|
296
|
+
{
|
|
297
|
+
'user_id': row['user_id'],
|
|
298
|
+
'message': row['message'],
|
|
299
|
+
'message_count': row['message_count'],
|
|
300
|
+
'timestamp': row['timestamp']
|
|
301
|
+
}
|
|
302
|
+
for row in cursor.fetchall()
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
def get_user_spam_history(self, guild_id: int, user_id: int,
|
|
306
|
+
hours: int = 24, limit: int = 50) -> List[Dict]:
|
|
307
|
+
"""Get spam history for a specific user within a time frame."""
|
|
308
|
+
cutoff_time = datetime.now() - timedelta(hours=hours)
|
|
309
|
+
|
|
310
|
+
with self.get_cursor() as cursor:
|
|
311
|
+
cursor.execute('''
|
|
312
|
+
SELECT message, message_count, timestamp
|
|
313
|
+
FROM spam_logs
|
|
314
|
+
WHERE guild_id = ? AND user_id = ? AND timestamp > ?
|
|
315
|
+
ORDER BY timestamp DESC LIMIT ?
|
|
316
|
+
''', (guild_id, user_id, cutoff_time, limit))
|
|
317
|
+
|
|
318
|
+
return [
|
|
319
|
+
{
|
|
320
|
+
'message': row['message'],
|
|
321
|
+
'message_count': row['message_count'],
|
|
322
|
+
'timestamp': row['timestamp']
|
|
323
|
+
}
|
|
324
|
+
for row in cursor.fetchall()
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
def clear_spam_logs(self, guild_id: int, older_than_days: Optional[int] = None) -> int:
|
|
328
|
+
"""Clear spam logs for a guild, optionally only older entries."""
|
|
329
|
+
with self.get_cursor() as cursor:
|
|
330
|
+
if older_than_days:
|
|
331
|
+
cutoff_date = datetime.now() - timedelta(days=older_than_days)
|
|
332
|
+
cursor.execute('''
|
|
333
|
+
DELETE FROM spam_logs
|
|
334
|
+
WHERE guild_id = ? AND timestamp < ?
|
|
335
|
+
''', (guild_id, cutoff_date))
|
|
336
|
+
else:
|
|
337
|
+
cursor.execute('DELETE FROM spam_logs WHERE guild_id = ?', (guild_id,))
|
|
338
|
+
|
|
339
|
+
deleted_count = cursor.rowcount
|
|
340
|
+
self.conn.commit()
|
|
341
|
+
return deleted_count
|
|
342
|
+
|
|
343
|
+
def add_to_whitelist(self, guild_id: int, user_id: int,
|
|
344
|
+
added_by: Optional[int] = None, reason: Optional[str] = None) -> bool:
|
|
345
|
+
"""Add user to spam whitelist with additional metadata."""
|
|
346
|
+
with self.get_cursor() as cursor:
|
|
347
|
+
columns = self._get_table_columns('spam_whitelist')
|
|
348
|
+
|
|
349
|
+
# Build query based on available columns
|
|
350
|
+
if 'added_by' in columns and 'reason' in columns:
|
|
351
|
+
cursor.execute('''
|
|
352
|
+
INSERT OR IGNORE INTO spam_whitelist (guild_id, user_id, added_by, reason)
|
|
353
|
+
VALUES (?, ?, ?, ?)
|
|
354
|
+
''', (guild_id, user_id, added_by, reason))
|
|
355
|
+
else:
|
|
356
|
+
cursor.execute('''
|
|
357
|
+
INSERT OR IGNORE INTO spam_whitelist (guild_id, user_id)
|
|
358
|
+
VALUES (?, ?)
|
|
359
|
+
''', (guild_id, user_id))
|
|
360
|
+
|
|
361
|
+
success = cursor.rowcount > 0
|
|
362
|
+
self.conn.commit()
|
|
363
|
+
return success
|
|
364
|
+
|
|
365
|
+
def remove_from_whitelist(self, guild_id: int, user_id: int) -> bool:
|
|
366
|
+
"""Remove user from spam whitelist."""
|
|
367
|
+
with self.get_cursor() as cursor:
|
|
368
|
+
cursor.execute('''
|
|
369
|
+
DELETE FROM spam_whitelist WHERE guild_id = ? AND user_id = ?
|
|
370
|
+
''', (guild_id, user_id))
|
|
371
|
+
success = cursor.rowcount > 0
|
|
372
|
+
self.conn.commit()
|
|
373
|
+
return success
|
|
374
|
+
|
|
375
|
+
def is_whitelisted(self, guild_id: int, user_id: int) -> bool:
|
|
376
|
+
"""Check if user is whitelisted."""
|
|
377
|
+
with self.get_cursor() as cursor:
|
|
378
|
+
cursor.execute('''
|
|
379
|
+
SELECT 1 FROM spam_whitelist WHERE guild_id = ? AND user_id = ?
|
|
380
|
+
''', (guild_id, user_id))
|
|
381
|
+
return cursor.fetchone() is not None
|
|
382
|
+
|
|
383
|
+
def get_whitelist(self, guild_id: int) -> List[Dict]:
|
|
384
|
+
"""Get all whitelisted users for a guild with metadata."""
|
|
385
|
+
with self.get_cursor() as cursor:
|
|
386
|
+
columns = self._get_table_columns('spam_whitelist')
|
|
387
|
+
|
|
388
|
+
# Build query based on available columns
|
|
389
|
+
select_columns = ['user_id']
|
|
390
|
+
if 'added_by' in columns:
|
|
391
|
+
select_columns.append('added_by')
|
|
392
|
+
if 'added_at' in columns:
|
|
393
|
+
select_columns.append('added_at')
|
|
394
|
+
if 'reason' in columns:
|
|
395
|
+
select_columns.append('reason')
|
|
396
|
+
|
|
397
|
+
query = f"SELECT {', '.join(select_columns)} FROM spam_whitelist WHERE guild_id = ? ORDER BY user_id"
|
|
398
|
+
cursor.execute(query, (guild_id,))
|
|
399
|
+
|
|
400
|
+
result = []
|
|
401
|
+
for row in cursor.fetchall():
|
|
402
|
+
entry = {'user_id': row['user_id']}
|
|
403
|
+
if 'added_by' in columns:
|
|
404
|
+
entry['added_by'] = row.get('added_by')
|
|
405
|
+
if 'added_at' in columns:
|
|
406
|
+
entry['added_at'] = row.get('added_at')
|
|
407
|
+
if 'reason' in columns:
|
|
408
|
+
entry['reason'] = row.get('reason')
|
|
409
|
+
result.append(entry)
|
|
410
|
+
|
|
411
|
+
return result
|
|
412
|
+
|
|
413
|
+
def get_guild_stats(self, guild_id: int) -> Dict:
|
|
414
|
+
"""Get comprehensive statistics for a guild."""
|
|
415
|
+
with self.get_cursor() as cursor:
|
|
416
|
+
# Get spam logs count
|
|
417
|
+
cursor.execute('SELECT COUNT(*) as total FROM spam_logs WHERE guild_id = ?', (guild_id,))
|
|
418
|
+
total_logs = cursor.fetchone()['total']
|
|
419
|
+
|
|
420
|
+
# Get recent spam (last 24 hours)
|
|
421
|
+
yesterday = datetime.now() - timedelta(hours=24)
|
|
422
|
+
cursor.execute('''
|
|
423
|
+
SELECT COUNT(*) as recent FROM spam_logs
|
|
424
|
+
WHERE guild_id = ? AND timestamp > ?
|
|
425
|
+
''', (guild_id, yesterday))
|
|
426
|
+
recent_logs = cursor.fetchone()['recent']
|
|
427
|
+
|
|
428
|
+
# Get whitelist count
|
|
429
|
+
cursor.execute('SELECT COUNT(*) as count FROM spam_whitelist WHERE guild_id = ?', (guild_id,))
|
|
430
|
+
whitelist_count = cursor.fetchone()['count']
|
|
431
|
+
|
|
432
|
+
# Get top spammers (last 7 days)
|
|
433
|
+
week_ago = datetime.now() - timedelta(days=7)
|
|
434
|
+
cursor.execute('''
|
|
435
|
+
SELECT user_id, COUNT(*) as spam_count, SUM(message_count) as total_messages
|
|
436
|
+
FROM spam_logs
|
|
437
|
+
WHERE guild_id = ? AND timestamp > ?
|
|
438
|
+
GROUP BY user_id
|
|
439
|
+
ORDER BY spam_count DESC
|
|
440
|
+
LIMIT 5
|
|
441
|
+
''', (guild_id, week_ago))
|
|
442
|
+
top_spammers = cursor.fetchall()
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
'total_spam_logs': total_logs,
|
|
446
|
+
'recent_spam_logs': recent_logs,
|
|
447
|
+
'whitelist_count': whitelist_count,
|
|
448
|
+
'top_spammers': [
|
|
449
|
+
{
|
|
450
|
+
'user_id': row['user_id'],
|
|
451
|
+
'spam_incidents': row['spam_count'],
|
|
452
|
+
'total_messages': row['total_messages']
|
|
453
|
+
}
|
|
454
|
+
for row in top_spammers
|
|
455
|
+
]
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
def cleanup_old_logs(self, days_to_keep: int = 30) -> int:
|
|
459
|
+
"""Clean up old spam logs across all guilds."""
|
|
460
|
+
cutoff_date = datetime.now() - timedelta(days=days_to_keep)
|
|
461
|
+
|
|
462
|
+
with self.get_cursor() as cursor:
|
|
463
|
+
cursor.execute('DELETE FROM spam_logs WHERE timestamp < ?', (cutoff_date,))
|
|
464
|
+
deleted_count = cursor.rowcount
|
|
465
|
+
self.conn.commit()
|
|
466
|
+
|
|
467
|
+
if deleted_count > 0:
|
|
468
|
+
self.logger.info(f"Cleaned up {deleted_count} old spam logs")
|
|
469
|
+
|
|
470
|
+
return deleted_count
|
|
471
|
+
|
|
472
|
+
def backup_database(self, backup_path: str) -> bool:
|
|
473
|
+
"""Create a backup of the database."""
|
|
474
|
+
try:
|
|
475
|
+
backup_conn = sqlite3.connect(backup_path)
|
|
476
|
+
self.conn.backup(backup_conn)
|
|
477
|
+
backup_conn.close()
|
|
478
|
+
return True
|
|
479
|
+
except Exception as e:
|
|
480
|
+
self.logger.error(f"Backup failed: {e}")
|
|
481
|
+
return False
|
|
482
|
+
|
|
483
|
+
def __enter__(self):
|
|
484
|
+
"""Context manager entry."""
|
|
485
|
+
return self
|
|
486
|
+
|
|
487
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
488
|
+
"""Context manager exit with proper cleanup."""
|
|
489
|
+
self.close()
|
|
490
|
+
|
|
491
|
+
def close(self):
|
|
492
|
+
"""Close database connection."""
|
|
493
|
+
if hasattr(self, 'conn') and self.conn:
|
|
494
|
+
self.conn.close()
|