d4rktg 1.2.4__py3-none-any.whl → 1.2.6__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/Database/db.py +1 -2
- d4rk/Handlers/__init__.py +2 -2
- d4rk/Handlers/_bot.py +31 -123
- d4rk/Handlers/_logger_bot.py +88 -0
- d4rk/Utils/_decorators.py +1 -15
- d4rk/Utils/_filters.py +7 -13
- d4rk/Utils/_movie_parser.py +2 -2
- d4rk/Web/__init__.py +4 -0
- d4rk/Web/web.py +201 -0
- d4rk/Web/web_server.py +52 -0
- d4rk/__init__.py +15 -2
- d4rk/_base.py +115 -0
- d4rk/_bot_manager.py +507 -0
- d4rk/errors/__init__.py +19 -0
- d4rk/errors/base.py +16 -0
- d4rk/errors/bot.py +33 -0
- d4rk/errors/config.py +17 -0
- d4rk/errors/database.py +22 -0
- {d4rktg-1.2.4.dist-info → d4rktg-1.2.6.dist-info}/METADATA +7 -1
- d4rktg-1.2.6.dist-info/RECORD +33 -0
- d4rk/Handlers/_scheduler.py +0 -33
- d4rktg-1.2.4.dist-info/RECORD +0 -23
- {d4rktg-1.2.4.dist-info → d4rktg-1.2.6.dist-info}/WHEEL +0 -0
- {d4rktg-1.2.4.dist-info → d4rktg-1.2.6.dist-info}/top_level.txt +0 -0
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']
|
d4rk/errors/__init__.py
ADDED
@@ -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}"
|
d4rk/errors/database.py
ADDED
@@ -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.
|
3
|
+
Version: 1.2.6
|
4
4
|
Summary: A module for create with easy and fast
|
5
5
|
Author: D4rkShell
|
6
6
|
Author-email: premiumqtrst@gmail.com
|
@@ -14,6 +14,12 @@ Requires-Dist: pyrofork==2.3.68
|
|
14
14
|
Requires-Dist: pymongo==4.14.1
|
15
15
|
Requires-Dist: python-dotenv==1.1.1
|
16
16
|
Requires-Dist: parse-torrent-title==2.8.1
|
17
|
+
Requires-Dist: colorama==0.4.6
|
18
|
+
Requires-Dist: cachetools==6.2.0
|
19
|
+
Requires-Dist: python-telegram-bot==22.3
|
20
|
+
Requires-Dist: aiohttp-jinja2==1.6
|
21
|
+
Requires-Dist: schedule==1.2.2
|
22
|
+
Requires-Dist: requests==2.32.4
|
17
23
|
Dynamic: author
|
18
24
|
Dynamic: author-email
|
19
25
|
Dynamic: classifier
|
@@ -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.6.dist-info/METADATA,sha256=SpfsAeR6R4guk5Qrj8UGp1uDKckVNMhYELsPpLR087c,973
|
31
|
+
d4rktg-1.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
32
|
+
d4rktg-1.2.6.dist-info/top_level.txt,sha256=qs1qTnKWImmGi7E0FoJS0OAEOHoVZA9vHRS3Pm6ncAo,5
|
33
|
+
d4rktg-1.2.6.dist-info/RECORD,,
|