FileToLink / Thunder /__main__.py
Fyaz Mohammed
patch 1
1b3eebd
Raw
History Blame Contribute Delete
13.2 kB
# Thunder/__main__.py
import asyncio
import glob
import importlib.util
import sys
from datetime import datetime
from pathlib import Path
if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
try:
from uvloop import install
install()
except ImportError:
pass
from aiohttp import web
from pyrogram import idle
from pyrogram.errors import FloodWait, MessageNotModified
from Thunder import __version__
from Thunder.bot import StreamBot
from Thunder.bot.clients import cleanup_clients, initialize_clients
from Thunder.server import web_server
from Thunder.utils.commands import set_commands
from Thunder.utils.database import db
from Thunder.utils.keepalive import ping_server
from Thunder.utils.canonical_files import drain_background_touch_tasks
from Thunder.utils.logger import logger
from Thunder.utils.messages import MSG_ADMIN_RESTART_DONE
from Thunder.utils.rate_limiter import rate_limiter, request_executor
from Thunder.utils.tokens import cleanup_expired_tokens
from Thunder.vars import Var
PLUGIN_PATH = "Thunder/bot/plugins/*.py"
VERSION = __version__
def print_banner():
banner = f"""
╔═══════════════════════════════════════════════════════════════════╗
║ ║
║ ████████╗██╗ ██╗██╗ ██╗███╗ ██╗██████╗ ███████╗██████╗ ║
║ ╚══██╔══╝██║ ██║██║ ██║████╗ ██║██╔══██╗██╔════╝██╔══██╗ ║
║ ██║ ███████║██║ ██║██╔██╗ ██║██║ ██║█████╗ ██████╔╝ ║
║ ██║ ██╔══██║██║ ██║██║╚██╗██║██║ ██║██╔══╝ ██╔══██╗ ║
║ ██║ ██║ ██║╚██████╔╝██║ ╚████║██████╔╝███████╗██║ ██║ ║
║ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝ ║
║ ║
║ File Streaming Bot v{VERSION}
╚═══════════════════════════════════════════════════════════════════╝
"""
print(banner)
def schedule_index_ensure() -> None:
task = asyncio.create_task(
db.ensure_indexes(raise_on_error=False),
name="ensure_database_indexes"
)
def _log_index_failure(done_task: asyncio.Task) -> None:
try:
created_indexes = done_task.result()
if created_indexes:
print(" ✓ Database indexes ensured.")
else:
print(" ▶ Database indexes could not be ensured during startup.")
except Exception as e:
logger.error(f"Background database index ensure failed: {e}", exc_info=True)
task.add_done_callback(_log_index_failure)
async def import_plugins():
print("╠════════════════════ IMPORTING PLUGINS ════════════════════╣")
plugins = glob.glob(PLUGIN_PATH)
if not plugins:
print(" ▶ No plugins found to import!")
return 0
success_count = 0
failed_plugins = []
for file_path in plugins:
try:
plugin_path = Path(file_path)
plugin_name = plugin_path.stem
import_path = f"Thunder.bot.plugins.{plugin_name}"
spec = importlib.util.spec_from_file_location(
import_path, plugin_path
)
if spec is None or spec.loader is None:
logger.error(f"Invalid plugin specification for {plugin_name}")
failed_plugins.append(plugin_name)
continue
module = importlib.util.module_from_spec(spec)
sys.modules[import_path] = module
spec.loader.exec_module(module)
success_count += 1
except Exception as e:
plugin_name = Path(file_path).stem
logger.error(f" ✖ Failed to import plugin {plugin_name}: {e}")
failed_plugins.append(plugin_name)
print(
f" ▶ Total: {len(plugins)} | Success: {success_count} | "
f"Failed: {len(failed_plugins)}"
)
if failed_plugins:
print(f" ▶ Failed plugins: {', '.join(failed_plugins)}")
return success_count
async def start_services():
start_time = datetime.now()
print_banner()
print("╔════════════════ INITIALIZING BOT SERVICES ════════════════╗")
print(" ▶ Starting Telegram Bot initialization...")
try:
try:
await StreamBot.start()
except FloodWait as e:
logger.debug(f"FloodWait in bot start, sleeping for {e.value}s")
await asyncio.sleep(e.value)
await StreamBot.start()
try:
bot_info = await StreamBot.get_me()
except FloodWait as e:
logger.debug(f"FloodWait in get_me, sleeping for {e.value}s")
await asyncio.sleep(e.value)
bot_info = await StreamBot.get_me()
StreamBot.username = bot_info.username
print(f" ✓ Bot initialized successfully as @{StreamBot.username}")
await set_commands()
print(" ✓ Bot commands set successfully.")
schedule_index_ensure()
restart_message_data = await db.get_restart_message()
if restart_message_data:
try:
try:
await StreamBot.edit_message_text(
chat_id=restart_message_data["chat_id"],
message_id=restart_message_data["message_id"],
text=MSG_ADMIN_RESTART_DONE,
)
except FloodWait as e:
logger.debug(f"FloodWait in restart message edit, sleeping for {e.value}s")
await asyncio.sleep(e.value)
await StreamBot.edit_message_text(
chat_id=restart_message_data["chat_id"],
message_id=restart_message_data["message_id"],
text=MSG_ADMIN_RESTART_DONE,
)
except MessageNotModified:
pass
await db.delete_restart_message(
restart_message_data["message_id"]
)
except Exception as e:
logger.error(
f"Error processing restart message: {e}", exc_info=True
)
else:
pass
except Exception as e:
logger.error(
f" ✖ Failed to initialize Telegram Bot: {e}", exc_info=True
)
return
print(" ▶ Starting Client initialization...")
try:
await initialize_clients()
except Exception as e:
logger.error(f" ✖ Failed to initialize clients: {e}", exc_info=True)
return
await import_plugins()
print(" ▶ Starting Request Executor initialization...")
try:
request_executor_task = asyncio.create_task(
request_executor(), name="request_executor_task"
)
print(" ✓ Request executor service started")
except Exception as e:
logger.error(
f" ✖ Failed to start request executor: {e}", exc_info=True
)
return
print(" ▶ Starting Web Server initialization...")
try:
app_runner = web.AppRunner(await web_server())
await app_runner.setup()
bind_address = Var.BIND_ADDRESS
site = web.TCPSite(app_runner, bind_address, Var.PORT)
await site.start()
keepalive_task = asyncio.create_task(
ping_server(), name="keepalive_task"
)
print(" ✓ Keep-alive service started")
token_cleanup_task = asyncio.create_task(
schedule_token_cleanup(), name="token_cleanup_task"
)
except Exception as e:
logger.error(f" ✖ Failed to start Web Server: {e}", exc_info=True)
if 'request_executor_task' in locals() and not request_executor_task.done():
request_executor_task.cancel()
try:
await request_executor_task
except asyncio.CancelledError:
pass
try:
await StreamBot.stop()
except Exception:
pass
try:
await cleanup_clients()
except Exception:
pass
try:
await rate_limiter.shutdown()
except Exception:
pass
try:
await db.close()
except Exception as e:
logger.error(f"Error during database cleanup: {e}", exc_info=True)
try:
await drain_background_touch_tasks()
except Exception as e:
logger.error(f"Error during canonical touch task cleanup: {e}", exc_info=True)
return
elapsed_time = (datetime.now() - start_time).total_seconds()
print("╠═══════════════════════════════════════════════════════════╣")
print(f" ▶ Bot Name: {bot_info.first_name}")
print(f" ▶ Username: @{bot_info.username}")
print(f" ▶ Server: {bind_address}:{Var.PORT}")
print(f" ▶ Startup Time: {elapsed_time:.2f} seconds")
print("╚═══════════════════════════════════════════════════════════╝")
print(" ▶ Bot is now running! Press CTRL+C to stop.")
background_tasks = [
request_executor_task,
keepalive_task,
token_cleanup_task
]
try:
await idle()
finally:
print(" ▶ Shutting down services...")
for task in background_tasks:
if not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
try:
await rate_limiter.shutdown()
except Exception as e:
logger.error(f"Error during rate limiter cleanup: {e}")
try:
await cleanup_clients()
except Exception as e:
logger.error(f"Error during client cleanup: {e}")
try:
await drain_background_touch_tasks()
except Exception as e:
logger.error(f"Error during canonical touch task cleanup: {e}", exc_info=True)
if 'app_runner' in locals() and app_runner is not None:
try:
await app_runner.cleanup()
except Exception as e:
logger.error(f"Error during web server cleanup: {e}")
try:
await db.close()
print(" ✓ Database connection closed")
except Exception as e:
logger.error("Error during database cleanup", exc_info=True)
async def schedule_token_cleanup():
while True:
try:
await asyncio.sleep(3 * 3600)
await cleanup_expired_tokens()
except asyncio.CancelledError:
logger.debug("schedule_token_cleanup cancelled cleanly.")
break
except Exception as e:
logger.error(f"Token cleanup error: {e}", exc_info=True)
if __name__ == '__main__':
try:
loop = asyncio.get_event_loop()
loop.run_until_complete(start_services())
except KeyboardInterrupt:
print("╔═══════════════════════════════════════════════════════════╗")
print("║ Bot stopped by user (CTRL+C) ║")
print("╚═══════════════════════════════════════════════════════════╝")
except Exception as e:
logger.error(f"An unexpected error occurred: {e}")