d4rktg 1.2.4__py3-none-any.whl → 1.2.5__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.
d4rk/_bot_manager.py ADDED
@@ -0,0 +1,507 @@
1
+ """
2
+ Bot Manager for d4rk package - Handles single or multiple bot instances using TGBase
3
+ """
4
+
5
+ import sys
6
+ import time
7
+ import signal
8
+ import asyncio
9
+ import threading
10
+
11
+ from typing import List
12
+ from datetime import datetime
13
+ from pyrogram.errors import FloodWait
14
+
15
+ from d4rk._base import TGBase
16
+ from d4rk.Web import WebServerManager
17
+ from d4rk.Handlers import LoggerBotUtil
18
+ from d4rk.Logs import setup_logger, get_timezone_offset
19
+
20
+ logger = setup_logger("D4RK_BotManager")
21
+
22
+ online_bots = {}
23
+ flood_waited_bots = {}
24
+ startup_message_id = None
25
+ startup_message_chat_id = None
26
+
27
+
28
+ class D4RK_BotManager:
29
+ def __init__(self,
30
+ app_name: str = None,
31
+ api_id: int = None,
32
+ api_hash: str = None,
33
+ tokens: List[str] = None,
34
+ max_bots_count: int = 4,
35
+ plugins: dict = None,
36
+ database_url: str = None,
37
+ database = None,
38
+ log_chat_id: int = None,
39
+ owner_id: int = None,
40
+ web_app_url: str = None,
41
+ web_server = None,
42
+ rename: bool = False,
43
+ logger_bot_token: str = None,
44
+ time_zone: str = "+5:30",
45
+ ) -> None:
46
+ """
47
+ Initialize the D4RK Bot Manager
48
+
49
+ Args:
50
+ api_id: Telegram API ID
51
+ api_hash: Telegram API Hash
52
+ tokens: List of bot tokens to use for running bots
53
+ max_bots_count: Maximum number of bots to run concurrently
54
+ app_name: Application name
55
+ plugins: Plugin configuration
56
+ database_url: Database connection URL
57
+ log_chat_id: Chat ID for logging
58
+ owner_id: Owner user ID
59
+ web_app_url: Web application URL
60
+ rename: Whether to enable renaming functionality
61
+ logger_bot_token: Token for the logger bot (uses python-telegram-bot library)
62
+ """
63
+ self.api_id = api_id
64
+ self.api_hash = api_hash
65
+ self.tokens = tokens or []
66
+ self.max_bots_count = max_bots_count
67
+ self.app_name = app_name
68
+ self.plugins = plugins
69
+ self.max_concurrent_bots = min(max_bots_count, len(self.tokens))
70
+ self.database_url = database_url
71
+ self.database = database
72
+ self.log_chat_id = log_chat_id
73
+ self.owner_id = owner_id
74
+ self.web_app_url = web_app_url
75
+ self._rename = rename
76
+ self.logger_bot_token = logger_bot_token
77
+
78
+ self.logger_bot_util = None
79
+ if self.logger_bot_token:
80
+ self.logger_bot_util = LoggerBotUtil()
81
+ self.logger_bot_util.set_token(self.logger_bot_token)
82
+
83
+ # Additional setup
84
+ self.TIME_ZONE = time_zone
85
+ self.TZ = get_timezone_offset(self.TIME_ZONE)
86
+ self.TZ_now = datetime.now(self.TZ)
87
+ self._stop_event = threading.Event()
88
+
89
+ # Bot management
90
+ self.bot_instances = [] # Store multiple bot instances
91
+ self.bot_instances_copy = [] # Copy for shutdown message
92
+ # Filter out logger bot token from available tokens
93
+ self.available_tokens = [t for t in self.tokens if t != self.logger_bot_token] if self.logger_bot_token else self.tokens.copy()
94
+ self.flood_waited_tokens = {} # {token: wait_until_timestamp}
95
+ self._running = False
96
+ self._shutdown_initiated = False
97
+ self._bot_counter = 0
98
+ self._main_loop = None
99
+ self.LOGS = self.log_chat_id
100
+ self.web_server_manager = web_server or WebServerManager(self)
101
+ self._web_runner = None
102
+
103
+ def run_bots(self):
104
+ """
105
+ Run multiple bots with max bot count and send status updates through logger bot.
106
+ First sends a message that logger bot started, then edits it with bot startup info.
107
+ """
108
+ # Set up signal handlers for graceful shutdown
109
+ signal.signal(signal.SIGINT, self._signal_handler)
110
+ signal.signal(signal.SIGTERM, self._signal_handler)
111
+
112
+ # Create and run event loop
113
+ try:
114
+ self._main_loop = asyncio.get_event_loop()
115
+ except RuntimeError:
116
+ self._main_loop = asyncio.new_event_loop()
117
+ asyncio.set_event_loop(self._main_loop)
118
+
119
+ try:
120
+ self._main_loop.run_until_complete(self._run_async())
121
+ except KeyboardInterrupt:
122
+ logger.info("Received interrupt signal, stopping bots...")
123
+ self._main_loop.run_until_complete(self.stop_all_bots())
124
+
125
+ def _signal_handler(self, signum, frame):
126
+ """Handle shutdown signals"""
127
+ logger.info(f"Received signal {signum}, stopping bots...")
128
+ # Just set the stop event and let the main loop handle the shutdown
129
+ self._running = False
130
+ self._stop_event.set()
131
+ logger.info("Shutdown process initiated")
132
+
133
+ # If we're in the main thread, don't exit immediately
134
+ # Let the main loop handle the shutdown
135
+ if threading.current_thread() is threading.main_thread():
136
+ # Don't exit immediately, let the main loop handle the shutdown
137
+ pass
138
+ else:
139
+ # If called from another thread, we can exit
140
+ sys.exit(0)
141
+
142
+ async def _run_async(self):
143
+ """Internal async method to run multiple bots with max bot count"""
144
+ global startup_message_id, startup_message_chat_id
145
+ self._running = True
146
+
147
+ # Initialize database once
148
+ await self._initialize_database()
149
+
150
+ # Setup web server
151
+ await self._setup_webserver()
152
+
153
+ # Send initial logger bot startup message
154
+ await self._send_logger_startup_message()
155
+
156
+ # Start multiple bot instances with token rotation on flood wait
157
+ bots_to_start = min(self.max_bots_count, len(self.available_tokens))
158
+ started_bots = 0
159
+ attempts = 0
160
+ max_attempts = 50 # Prevent infinite loop
161
+
162
+ while started_bots < bots_to_start and attempts < max_attempts and (self.available_tokens or self.flood_waited_tokens):
163
+ attempts += 1
164
+
165
+ # Check if we have available tokens
166
+ if self.available_tokens:
167
+ token = self.available_tokens.pop(0)
168
+ success = await self._start_bot_instance(token)
169
+ if success:
170
+ started_bots += 1
171
+ else:
172
+ # If the bot failed to start, we need to decrement the bot counter
173
+ # since we incremented it in _start_bot_instance but the bot didn't start
174
+ if self._bot_counter > 0:
175
+ self._bot_counter -= 1
176
+ else:
177
+ # No available tokens, check if any flood wait tokens have expired
178
+ current_time = time.time()
179
+ expired_tokens = []
180
+ for token, wait_until in self.flood_waited_tokens.items():
181
+ if current_time >= wait_until:
182
+ expired_tokens.append(token)
183
+
184
+ # Move expired tokens back to available pool
185
+ for token in expired_tokens:
186
+ del self.flood_waited_tokens[token]
187
+ self.available_tokens.append(token)
188
+ logger.info(f"Token {token[:10]}... is no longer flood waited")
189
+
190
+ # If we moved some tokens back, continue the loop
191
+ if expired_tokens:
192
+ continue
193
+
194
+ # Check if we have other tokens in the original tokens list that are not in flood wait
195
+ # This implements actual token rotation
196
+ for token in self.tokens:
197
+ if token not in self.flood_waited_tokens and token not in self.available_tokens:
198
+ # Check if this token is already being used by a running bot
199
+ token_in_use = False
200
+ for bot_instance in self.bot_instances:
201
+ if bot_instance.token == token:
202
+ token_in_use = True
203
+ break
204
+ if not token_in_use:
205
+ self.available_tokens.append(token)
206
+ logger.info(f"Added fresh token {token[:10]}... for rotation")
207
+ break
208
+
209
+ # If we still don't have available tokens, wait a bit
210
+ if not self.available_tokens:
211
+ logger.info("No available tokens, waiting for flood wait expiration or new tokens...")
212
+ await asyncio.sleep(10)
213
+
214
+ logger.info(f"Attempted to start bots, {len(self.bot_instances)} bot instances successfully started")
215
+
216
+ # Update startup message with online bots
217
+ await self._update_startup_message_with_online_bots()
218
+
219
+ # Monitor and manage bots
220
+ try:
221
+ while self._running and not self._stop_event.is_set():
222
+ await self._manage_bots()
223
+ await asyncio.sleep(5) # Check every 5 seconds
224
+ except Exception as e:
225
+ logger.error(f"Error in bot management: {e}")
226
+ finally:
227
+ await self.stop_all_bots()
228
+
229
+ async def _initialize_database(self):
230
+ """Initialize database connection"""
231
+ try:
232
+ self.database.connect(name=self.app_name, DATABASE_URL=self.database_url)
233
+ except Exception as e:
234
+ logger.error(f"Failed to initialize database: {e}")
235
+ async def _setup_webserver(self):
236
+ """Setup web server"""
237
+ try:
238
+ self._web_runner = await self.web_server_manager.setup_web_server(8443)
239
+ except Exception as e:
240
+ logger.error(f"Failed to setup web server: {e}")
241
+
242
+ async def _manage_bots(self):
243
+ """Manage bots, handle flood waits, and token rotation"""
244
+ current_time = time.time()
245
+
246
+ # Check for expired flood waits
247
+ expired_tokens = []
248
+ for token, wait_until in self.flood_waited_tokens.items():
249
+ if current_time >= wait_until:
250
+ expired_tokens.append(token)
251
+
252
+ # Move expired tokens back to available pool
253
+ for token in expired_tokens:
254
+ del self.flood_waited_tokens[token]
255
+ self.available_tokens.append(token)
256
+ logger.info(f"Token {token[:10]}... is no longer flood waited")
257
+
258
+ # Check if we can start more bots
259
+ while (len(self.bot_instances) < self.max_bots_count and
260
+ (self.available_tokens or self.flood_waited_tokens)):
261
+ if self.available_tokens:
262
+ # Use an available token
263
+ token = self.available_tokens.pop(0)
264
+ success = await self._start_bot_instance(token)
265
+ # If failed due to flood wait, the token is already handled in _start_bot_instance
266
+ # If successful, the bot is already added to self.bot_instances
267
+ else:
268
+ # No available tokens, try to add fresh tokens for rotation
269
+ # Check if we have other tokens in the original tokens list that are not in flood wait
270
+ for token in self.tokens:
271
+ if token not in self.flood_waited_tokens and token not in self.available_tokens:
272
+ # Check if this token is already being used by a running bot
273
+ token_in_use = False
274
+ for bot_instance in self.bot_instances:
275
+ if bot_instance.token == token:
276
+ token_in_use = True
277
+ break
278
+ if not token_in_use:
279
+ self.available_tokens.append(token)
280
+ logger.info(f"Added fresh token {token[:10]}... for rotation in manage_bots")
281
+ break
282
+
283
+ # If we still don't have available tokens, wait a bit and check again
284
+ if not self.available_tokens:
285
+ break
286
+
287
+ async def _send_logger_startup_message(self):
288
+ """Send initial logger bot startup message"""
289
+ global startup_message_id, startup_message_chat_id
290
+
291
+ # Only send the initial message if it hasn't been sent yet
292
+ if startup_message_id is None and self.LOGS and self.logger_bot_util:
293
+ try:
294
+ message = await self.logger_bot_util.send_log_message(
295
+ chat_id=self.LOGS,
296
+ message=f"✨ [ {str(self.app_name).upper()} ONLINE ] ✨\n\n"
297
+ f"🚀 Logger Bot: ✅ Active\n"
298
+ f"🗄️ Database: 🟢 Connected\n"
299
+ f"🌐 Web Server: 🔥 Running on Port 8443\n\n"
300
+ f"🤖 Starting {min(self.max_bots_count, len(self.available_tokens))}",
301
+ parse_mode="HTML"
302
+ )
303
+ if message:
304
+ startup_message_id = message.message_id
305
+ startup_message_chat_id = message.chat.id
306
+ else:
307
+ logger.error("Failed to send logger startup message: No message returned")
308
+ except Exception as e:
309
+ logger.error(f"Failed to send logger startup message: {e}")
310
+
311
+ async def _update_startup_message_with_online_bots(self):
312
+ """Update startup message with information about online bots"""
313
+ global startup_message_id, startup_message_chat_id
314
+
315
+ if startup_message_id and startup_message_chat_id and self.LOGS and self.logger_bot_util:
316
+ try:
317
+ # Get online bot information
318
+ online_bot_count = len(self.bot_instances)
319
+ bot_info = ""
320
+ if online_bot_count > 0:
321
+ bot_info = "\n"
322
+ for i, bot_instance in enumerate(self.bot_instances, 1):
323
+ try:
324
+ # Get bot info to mention it properly
325
+ bot_me = await bot_instance.get_me()
326
+ bot_name = bot_me.first_name if bot_me.first_name else "Unknown Bot"
327
+ bot_id = bot_me.id if bot_me.id else "Unknown"
328
+
329
+ # Store bot info for later use during shutdown
330
+ bot_instance._bot_name = bot_name
331
+ bot_instance._bot_id = bot_id
332
+
333
+ if i == len(self.bot_instances):
334
+ bot_info += f" └─ ⚡ <a href='tg://user?id={bot_id}'>{bot_name}</a>\n"
335
+ else:
336
+ bot_info += f" ├─ ⚡ <a href='tg://user?id={bot_id}'>{bot_name}</a>\n"
337
+ except Exception as e:
338
+ # Fallback to token-based identification if we can't get bot info
339
+ token_suffix = bot_instance.token[-8:] if bot_instance.token else "Unknown"
340
+ bot_instance._bot_name = f"Bot ...{token_suffix}"
341
+ bot_instance._bot_id = "Unknown"
342
+ bot_info += f"Bot ...{token_suffix}\n"
343
+
344
+ # Update the message with HTML parsing for mentions
345
+
346
+ await self.logger_bot_util.edit_log_message(
347
+ chat_id=startup_message_chat_id,
348
+ message_id=startup_message_id,
349
+ message=
350
+ f"✨ [ {str(self.app_name).upper()} ONLINE ] ✨\n\n"
351
+ f"🚀 Logger Bot: ✅ Active\n"
352
+ f"🗄️ Database: 🟢 Connected\n"
353
+ f"🌐 Web Server: 🔥 Running on Port 8443\n\n"
354
+ f"🤖 Active Bots: {online_bot_count}" + bot_info,
355
+ parse_mode="HTML"
356
+ )
357
+ except Exception as e:
358
+ logger.error(f"Failed to update startup message with online bots: {e}")
359
+
360
+ async def _start_bot_instance(self, token: str):
361
+ """Start a bot instance with a specific token"""
362
+ try:
363
+ self._bot_counter += 1
364
+ # Ensure unique bot names by using the main manager's app_name as prefix
365
+ bot_name = f"{self.app_name}_{self._bot_counter}" if self.app_name else f"bot_{self._bot_counter}"
366
+
367
+ # Create a new bot instance using TGBase
368
+ bot_instance = TGBase(
369
+ api_id=self.api_id,
370
+ api_hash=self.api_hash,
371
+ token=token,
372
+ app_name=bot_name,
373
+ owner_id=self.owner_id,
374
+ plugins=self.plugins,
375
+ database_url=self.database_url,
376
+ log_chat_id=self.log_chat_id,
377
+ rename=self._rename,
378
+ logger_bot_util=self.logger_bot_util # Pass logger bot util to each bot
379
+ )
380
+
381
+ # Mark this as a bot instance to prevent it from trying to manage other bots
382
+ bot_instance._is_bot_instance = True
383
+
384
+ # Start the bot instance
385
+ await bot_instance._start_single_bot()
386
+
387
+ # Store bot reference
388
+ self.bot_instances.append(bot_instance)
389
+
390
+ # Only log success after the bot is actually started
391
+ logger.info(f"Started bot instance {bot_name} with token {token[:10]}...")
392
+
393
+ # Return True to indicate that the bot was started successfully
394
+ return True
395
+
396
+ except FloodWait as e:
397
+ logger.error(f"FloodWait encountered for token {token[:10]}: {e.value} seconds")
398
+ # Add token to flood waited tokens
399
+ self.flood_waited_tokens[token] = time.time() + e.value
400
+ logger.info(f"Token {token[:10]} added to flood wait list, will retry later")
401
+ # Return False to indicate that the bot was not started
402
+ return False
403
+
404
+ except Exception as e:
405
+ logger.error(f"Failed to start bot instance with token {token[:10]}...: {e}")
406
+ # Return False to indicate that the bot was not started
407
+ return False
408
+
409
+ async def stop_all_bots(self):
410
+ """Stop all bot instances gracefully"""
411
+ global startup_message_id, startup_message_chat_id, online_bots, flood_waited_bots
412
+
413
+ # Check if already stopping to prevent duplicate calls
414
+ if not getattr(self, '_shutdown_initiated', False):
415
+ logger.info("Stopping all bot instances...")
416
+ self._shutdown_initiated = True
417
+ else:
418
+ logger.info("Bot shutdown already initiated, skipping...")
419
+ return
420
+
421
+ self._running = False
422
+ self._stop_event.set()
423
+
424
+ # Edit startup message to show shutdown in progress with 🔄 emoji for stopping bots
425
+ if startup_message_id and startup_message_chat_id and self.LOGS and self.logger_bot_util:
426
+ try:
427
+ # Create bot info with 🔄 emoji for stopping bots using saved information
428
+ bot_info = ""
429
+ if len(self.bot_instances) > 0:
430
+ bot_info = "\n"
431
+ for i, bot_instance in enumerate(self.bot_instances, 1):
432
+
433
+ # Use saved bot info from when the bot was running
434
+ bot_name = getattr(bot_instance, '_bot_name', f"Bot ...{bot_instance.token[-8:] if bot_instance.token else 'Unknown'}")
435
+ bot_id = getattr(bot_instance, '_bot_id', 'Unknown')
436
+ if i==len(self.bot_instances):bot_info += f" └─ 🔄 <a href='tg://user?id={bot_id}'>{bot_name}</a>\n"
437
+ else:bot_info += f" ├─ 🔄 <a href='tg://user?id={bot_id}'>{bot_name}</a>\n"
438
+
439
+ await self.logger_bot_util.edit_log_message(
440
+ chat_id=startup_message_chat_id,
441
+ message_id=startup_message_id,
442
+ message=f"✨ [ {str(self.app_name).upper()} ONLINE ] ✨\n\n"
443
+ f"🚀 Logger Bot: ✅ Active\n"
444
+ f"🗄️ Database: 🟢 Connected\n"
445
+ f"🌐 Web Server: 🔥 Stopping on Port 8443\n\n"
446
+ f"🤖 Stopping Bots: {len(self.bot_instances)}" + bot_info,
447
+ parse_mode="HTML"
448
+ )
449
+ except Exception as e:
450
+ logger.error(f"Failed to update shutdown progress message: {e}")
451
+
452
+ # Stop all bot instances
453
+ stop_tasks = []
454
+ for bot_instance in self.bot_instances:
455
+ try:
456
+ stop_tasks.append(bot_instance._stop_single_bot())
457
+ except Exception as e:
458
+ logger.error(f"Error preparing to stop bot instance: {e}")
459
+
460
+ # Wait for all bot instances to stop
461
+ if stop_tasks:
462
+ try:
463
+ await asyncio.gather(*stop_tasks, return_exceptions=True)
464
+ except Exception as e:
465
+ logger.error(f"Error while stopping bot instances: {e}")
466
+
467
+ # Store the bot count and copy of bot instances before clearing the list
468
+ stopped_bot_count = len(self.bot_instances)
469
+ # Create a copy of the bot instances list before clearing it
470
+ self.bot_instances_copy = self.bot_instances.copy()
471
+ self.bot_instances.clear()
472
+
473
+ # Edit startup message to show successful shutdown with 🛑 emoji for stopped bots
474
+ if startup_message_id and startup_message_chat_id and self.LOGS and self.logger_bot_util:
475
+ try:
476
+ # Create bot info with 🛑 emoji for stopped bots using saved information
477
+ bot_info = ""
478
+ if stopped_bot_count > 0:
479
+ bot_info = "\n"
480
+
481
+ for i, bot_instance in enumerate(self.bot_instances_copy, 1):
482
+ # Use saved bot info from when the bot was running
483
+ bot_name = getattr(bot_instance, '_bot_name', f"Bot ...{bot_instance.token[-8:] if bot_instance.token else 'Unknown'}")
484
+ bot_id = getattr(bot_instance, '_bot_id', 'Unknown')
485
+ if i==len(self.bot_instances_copy):bot_info += f" └─ 🛑 <a href='tg://user?id={bot_id}'>{bot_name}</a>\n"
486
+ else:bot_info += f" ├─ 🛑 <a href='tg://user?id={bot_id}'>{bot_name}</a>\n"
487
+
488
+ await self.logger_bot_util.edit_log_message(
489
+ chat_id=startup_message_chat_id,
490
+ message_id=startup_message_id,
491
+ message=f"✨ [ {self.app_name} OFFLINE ] ✨\n\n"
492
+ f"🚀 Logger Bot: 📛 Deactivated\n"
493
+ f"🗄️ Database: 📛 Disonnected\n"
494
+ f"🌐 Web Server: ⏸️ Stopped\n\n"
495
+ f"🤖 All {stopped_bot_count}" + bot_info,
496
+ parse_mode="HTML"
497
+ )
498
+ except Exception as e:
499
+ logger.error(f"Failed to update successful shutdown message: {e}")
500
+
501
+ # Clear global tracking variables
502
+ startup_message_id = None
503
+ startup_message_chat_id = None
504
+
505
+ logger.info("All bot instances stopped successfully")
506
+
507
+ __all__ = ['D4RK_BotManager']
@@ -0,0 +1,19 @@
1
+ """
2
+ D4rk Errors Module
3
+ Custom exception classes for the D4rk package
4
+ """
5
+
6
+ from .base import D4rkError
7
+ from .bot import BotError, FloodWaitError, TokenError
8
+ from .database import DatabaseError, ConnectionError
9
+ from .config import ConfigError
10
+
11
+ __all__ = [
12
+ 'D4rkError',
13
+ 'BotError',
14
+ 'FloodWaitError',
15
+ 'TokenError',
16
+ 'DatabaseError',
17
+ 'ConnectionError',
18
+ 'ConfigError'
19
+ ]
d4rk/errors/base.py ADDED
@@ -0,0 +1,16 @@
1
+ """
2
+ Base exception classes for D4rk package
3
+ """
4
+
5
+ class D4rkError(Exception):
6
+ """Base exception class for all D4rk package errors"""
7
+
8
+ def __init__(self, message: str = None, *args):
9
+ self.message = message
10
+ super().__init__(message, *args)
11
+
12
+ def __str__(self):
13
+ return self.message or "An error occurred in the D4rk package"
14
+
15
+ def __repr__(self):
16
+ return f"{self.__class__.__name__}({self.message!r})"
d4rk/errors/bot.py ADDED
@@ -0,0 +1,33 @@
1
+ """
2
+ Bot-specific exception classes for D4rk package
3
+ """
4
+
5
+ from .base import D4rkError
6
+
7
+ class BotError(D4rkError):
8
+ """Base exception class for bot-related errors"""
9
+ pass
10
+
11
+ class FloodWaitError(BotError):
12
+ """Exception raised when Telegram API returns a flood wait error"""
13
+
14
+ def __init__(self, message: str = None, wait_time: int = None):
15
+ super().__init__(message)
16
+ self.wait_time = wait_time
17
+
18
+ def __str__(self):
19
+ if self.wait_time:
20
+ return f"Flood wait error: {self.message} (wait {self.wait_time} seconds)"
21
+ return f"Flood wait error: {self.message}"
22
+
23
+ class TokenError(BotError):
24
+ """Exception raised when there are issues with bot tokens"""
25
+
26
+ def __init__(self, message: str = None, token: str = None):
27
+ super().__init__(message)
28
+ self.token = token
29
+
30
+ def __str__(self):
31
+ if self.token:
32
+ return f"Token error: {self.message} (token: {self.token[:10]}...)"
33
+ return f"Token error: {self.message}"
d4rk/errors/config.py ADDED
@@ -0,0 +1,17 @@
1
+ """
2
+ Configuration exception classes for D4rk package
3
+ """
4
+
5
+ from .base import D4rkError
6
+
7
+ class ConfigError(D4rkError):
8
+ """Base exception class for configuration-related errors"""
9
+
10
+ def __init__(self, message: str = None, config_key: str = None):
11
+ super().__init__(message)
12
+ self.config_key = config_key
13
+
14
+ def __str__(self):
15
+ if self.config_key:
16
+ return f"Configuration error: {self.message} (key: {self.config_key})"
17
+ return f"Configuration error: {self.message}"
@@ -0,0 +1,22 @@
1
+ """
2
+ Database exception classes for D4rk package
3
+ """
4
+
5
+ from .base import D4rkError
6
+
7
+ class DatabaseError(D4rkError):
8
+ """Base exception class for database-related errors"""
9
+ pass
10
+
11
+ class ConnectionError(DatabaseError):
12
+ """Exception raised when database connection fails"""
13
+
14
+ def __init__(self, message: str = None, host: str = None, port: int = None):
15
+ super().__init__(message)
16
+ self.host = host
17
+ self.port = port
18
+
19
+ def __str__(self):
20
+ if self.host and self.port:
21
+ return f"Database connection error: {self.message} (host: {self.host}, port: {self.port})"
22
+ return f"Database connection error: {self.message}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: d4rktg
3
- Version: 1.2.4
3
+ Version: 1.2.5
4
4
  Summary: A module for create with easy and fast
5
5
  Author: D4rkShell
6
6
  Author-email: premiumqtrst@gmail.com
@@ -0,0 +1,33 @@
1
+ d4rk/__init__.py,sha256=X2qHrmURAyqZQ3j6YiwMAmzGZ-itFSlaCrFoir7PDvs,444
2
+ d4rk/_base.py,sha256=GG7t4DUN3ECW100IWfgmEM_AVImFHz2RU8vJZwJWbKw,4134
3
+ d4rk/_bot_manager.py,sha256=fM6kwGn4ofby94YlcQFztdUzEZjX4-IRe2kd2Fbm4WM,24211
4
+ d4rk/Database/__init__.py,sha256=TQB5D8PBDCq80jPq6rsC2G939yYYKTh_bCcOWsZ-nA8,18
5
+ d4rk/Database/db.py,sha256=r0mjeT_oBWZcvwxFrF9dYNuqGYxwZnaTTtBJiNDhHHo,1806
6
+ d4rk/Handlers/__init__.py,sha256=nZKgSx0KZrJJkE5VV2GOWdLgiCiEFSWyfTHl8GVkW-Q,107
7
+ d4rk/Handlers/_bot.py,sha256=-C8kw-FXLvthXIWWL4WUUkhzDsFpx2PD3bTnwr9sDuU,4164
8
+ d4rk/Handlers/_custom.py,sha256=ibOW0mqQD-oMy9wFnSehEHOeJw2tkkg4dM77qglNsVg,7374
9
+ d4rk/Handlers/_logger_bot.py,sha256=xIY4LkjChF7_uY7lOs0f2Ua38QInFbaGShHNTaugAvo,3581
10
+ d4rk/Logs/__init__.py,sha256=mXWD5jXnyH3_AvS7K_ki3iw5BpoEAvrDFbmr-iEFNnY,22
11
+ d4rk/Logs/_logger.py,sha256=lqfVvCO0vZ_IaGOdIE4HA2KAUQZh7yW2iAHZcBz7F4o,4120
12
+ d4rk/Utils/__init__.py,sha256=osxmPOxP1Srwh4z3i1qsAwsBNQYjonTHQc_IAZ8eNU0,397
13
+ d4rk/Utils/_buttons.py,sha256=gehLWh0NOQSkSNAIuUBEJ8jN2uzNDail2tJboV4656w,3311
14
+ d4rk/Utils/_decorators.py,sha256=ISkNDkOdjCgn_LsSw6TVGjeP9XVmvFpTsv5w-ioP1lI,4403
15
+ d4rk/Utils/_filters.py,sha256=4RTJP7_dPFSbpW0I4smNkEeYderPgkin5SVvir7OtOc,2586
16
+ d4rk/Utils/_fonts.py,sha256=4zpjAmhAhRGzkSaD1b80p_bpwF72mOe7ykcpJFc688w,3690
17
+ d4rk/Utils/_ip.py,sha256=KJJW2QSngshIVWCO5YPXF1wj4IPQzVN5oFofpfzlU5w,559
18
+ d4rk/Utils/_movie_parser.py,sha256=gLwkCsg76281cbRXN8s9nJG-JVPzDu4WlbsZHYriRy4,7389
19
+ d4rk/Utils/_ractions.py,sha256=wOVPyoFnbDuMgoP6NF_gLO1DYcfhERC0trdAK1jWSE8,2170
20
+ d4rk/Utils/_terminal.py,sha256=Anu4OcffY3v6LMOrCskP1cHrJIliomo1Hjownbhh2sQ,125
21
+ d4rk/Utils/_utils.py,sha256=z1fhMFMDKEBRYr6l6TbdqK3pEuPEuQ-CtKUiO7R-R_A,332
22
+ d4rk/Web/__init__.py,sha256=2ak1iz-ngFmaJTrYXCmyG8Fihu9U3DgY3yp8Y2nOj4s,95
23
+ d4rk/Web/web.py,sha256=lnAy_OLx_yprkXfsJIgjgra9JOZHsXHHhbjrty_j2wQ,5401
24
+ d4rk/Web/web_server.py,sha256=pUrtPXNRPMJPixEo1Y4qy11laBOS7GXPV6goA-FmJ5o,1877
25
+ d4rk/errors/__init__.py,sha256=QWvrGtmNCUINHRcH1GbDptdB6lYZjiv6wEOfXBTZX5s,409
26
+ d4rk/errors/base.py,sha256=64VqGMLMOqZIx5Zk0kjWmk98f3kGRtxdPIDKsjPmyLI,469
27
+ d4rk/errors/bot.py,sha256=HOBj81C7OUOVTA9kFwJUkSz8HNteK2nRc5-FLcOq2MM,1048
28
+ d4rk/errors/config.py,sha256=fFXN2W7FwL3avGolx-H3H9fehQqjy5zTeVNs6VVUz3k,534
29
+ d4rk/errors/database.py,sha256=pFYERbi2yUZkmiqP8kVtwxsBHdqa8Ljjp_-0BbwUs-4,696
30
+ d4rktg-1.2.5.dist-info/METADATA,sha256=WtuhzvZ9O6MjcQd1QlWS3RSWMzLlcGzsPdb-21jriRI,764
31
+ d4rktg-1.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
+ d4rktg-1.2.5.dist-info/top_level.txt,sha256=qs1qTnKWImmGi7E0FoJS0OAEOHoVZA9vHRS3Pm6ncAo,5
33
+ d4rktg-1.2.5.dist-info/RECORD,,