sabouserbot / multi_client.py
xdragxt's picture
Create multi_client.py
c723e4d verified
#!/usr/bin/env python3
"""
Simplified Multi-Client Ultroid Launcher v2
Uses working directories but with minimal setup
"""
import os
import subprocess
import sys
import shutil
# Load .env file
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
REQUIRED_VARS = ["API_ID", "API_HASH", "SESSION", "MONGO_URI"]
def has_client_config(client_num):
"""Check if client has all required config"""
suffix = "" if client_num == 1 else str(client_num - 1)
for var in REQUIRED_VARS:
if not os.environ.get(var + suffix):
return False
return True
def stop_client(client_num):
"""Stop a running client if it exists"""
base_dir = os.getcwd()
pid_file = os.path.join(base_dir, f"client_{client_num}.pid")
if os.path.exists(pid_file):
try:
with open(pid_file, 'r') as f:
pid = int(f.read().strip())
# Try to kill the process
try:
if sys.platform == "win32":
subprocess.run(["taskkill", "/F", "/PID", str(pid)],
capture_output=True, check=False)
else:
os.kill(pid, 15) # SIGTERM
print(f" β†’ Stopped Client {client_num} (PID: {pid})")
except (ProcessLookupError, OSError):
# Process already dead
pass
# Remove PID file
try:
os.remove(pid_file)
except:
pass
except Exception:
pass
# Also try to kill by process detection (psutil if available)
try:
import psutil
for proc in psutil.process_iter(['pid', 'cmdline', 'cwd']):
try:
cmdline = proc.info.get('cmdline', [])
proc_cwd = proc.info.get('cwd', '')
# Check if this is a pyUltroid process in client_N directory
if any('pyUltroid' in str(arg) for arg in cmdline):
if f'client_{client_num}' in proc_cwd:
proc.kill()
print(f" β†’ Stopped Client {client_num} (PID: {proc.info['pid']})")
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
except ImportError:
# psutil not available, skip process detection
pass
def setup_client_dir(client_num, base_dir):
"""Minimal setup for client directory"""
client_dir = os.path.join(base_dir, f"client_{client_num}")
os.makedirs(client_dir, exist_ok=True)
# Only create symlinks for essential directories
for dir_name in ["plugins", "resources", "assistant", "strings", "addons"]:
src = os.path.join(base_dir, dir_name)
dst = os.path.join(client_dir, dir_name)
if os.path.exists(src) and not os.path.exists(dst):
try:
if sys.platform != "win32":
os.symlink(os.path.abspath(src), dst)
else:
# Windows: copy instead of symlink
if os.path.isdir(src):
shutil.copytree(src, dst)
except Exception:
pass
# Copy .env
if os.path.exists(".env"):
shutil.copy(".env", os.path.join(client_dir, ".env"))
return client_dir
def start_client(client_num):
"""Start a client"""
if not has_client_config(client_num):
suffix = "" if client_num == 1 else str(client_num - 1)
missing = [v for v in REQUIRED_VARS if not os.environ.get(v + suffix)]
print(f"βœ— Client {client_num}: Missing {', '.join(missing)}")
return None
print(f"βœ“ Client {client_num}: Starting...")
base_dir = os.getcwd()
client_dir = setup_client_dir(client_num, base_dir)
# Build environment
env = os.environ.copy()
suffix = "" if client_num == 1 else str(client_num - 1)
# Get values
api_id = os.environ.get(f"API_ID{suffix}") or os.environ.get("API_ID")
api_hash = os.environ.get(f"API_HASH{suffix}") or os.environ.get("API_HASH")
session = os.environ.get(f"SESSION{suffix}") or os.environ.get("SESSION")
mongo_uri = os.environ.get(f"MONGO_URI{suffix}") or os.environ.get("MONGO_URI")
# Unique database
db_name = f"UltroidDB{client_num}"
if mongo_uri.endswith('/'):
env["MONGO_URI"] = f"{mongo_uri}{db_name}"
else:
env["MONGO_URI"] = f"{mongo_uri}/{db_name}"
# Optional vars
if os.environ.get(f"LOG_CHANNEL{suffix}"):
env["LOG_CHANNEL"] = os.environ.get(f"LOG_CHANNEL{suffix}")
elif os.environ.get("LOG_CHANNEL"):
env["LOG_CHANNEL"] = os.environ.get("LOG_CHANNEL")
if os.environ.get(f"BOT_TOKEN{suffix}"):
env["BOT_TOKEN"] = os.environ.get(f"BOT_TOKEN{suffix}")
elif os.environ.get("BOT_TOKEN"):
env["BOT_TOKEN"] = os.environ.get("BOT_TOKEN")
env["PYTHONPATH"] = base_dir
# Note: The .restart command will work correctly because:
# - Working directory (client_dir) is preserved by os.execl()
# - Environment variables (PYTHONPATH, MONGO_URI, etc.) are preserved
# - sys.argv arguments (API_ID, API_HASH, SESSION) are preserved
# The restart function in pyUltroid/fns/helper.py handles this automatically
try:
proc = subprocess.Popen(
[sys.executable, "-m", "pyUltroid", api_id, api_hash, session, "", ""],
cwd=client_dir,
env=env,
)
# Save PID file
pid_file = os.path.join(base_dir, f"client_{client_num}.pid")
try:
with open(pid_file, 'w') as f:
f.write(str(proc.pid))
except:
pass
print(f" β†’ Started (PID: {proc.pid})")
return proc
except Exception as e:
print(f" βœ— Error: {e}")
return None
def main():
print("=" * 60)
print("Multi-Client Ultroid Launcher v2 (Simplified)")
print("=" * 60)
print()
# First, stop clients that don't have config
print("Checking for clients without configuration...")
stopped_count = 0
for i in range(1, 6):
if not has_client_config(i):
# Check if this client is running and stop it
base_dir = os.getcwd()
pid_file = os.path.join(base_dir, f"client_{i}.pid")
if os.path.exists(pid_file):
stop_client(i)
stopped_count += 1
if stopped_count > 0:
print(f"βœ“ Stopped {stopped_count} client(s) without configuration")
print()
# Now start clients with proper config
processes = []
for i in range(1, 6):
proc = start_client(i)
if proc:
processes.append((i, proc))
print()
if not processes:
print("βœ— No clients started. Check your .env file.")
sys.exit(1)
print("=" * 60)
print(f"βœ“ {len(processes)} client(s) running")
for num, proc in processes:
print(f" Client {num}: PID {proc.pid}")
print("=" * 60)
print("\nPress Ctrl+C to exit (clients keep running)")
print("Monitoring clients - will stop any that lose configuration...")
try:
import time
while True:
time.sleep(30) # Check every 30 seconds
# Check all clients and stop those without config
for i in range(1, 6):
if not has_client_config(i):
# Check if this client is still running
base_dir = os.getcwd()
pid_file = os.path.join(base_dir, f"client_{i}.pid")
if os.path.exists(pid_file):
try:
with open(pid_file, 'r') as f:
pid = int(f.read().strip())
# Check if process is still alive
try:
if sys.platform == "win32":
result = subprocess.run(["tasklist", "/FI", f"PID eq {pid}"],
capture_output=True, check=False)
if str(pid) in result.stdout.decode():
# Process exists, kill it
subprocess.run(["taskkill", "/F", "/PID", str(pid)],
capture_output=True, check=False)
print(f"\n⚠ Client {i} stopped (configuration removed)")
else:
os.kill(pid, 0) # Check if process exists
# Process exists, kill it
os.kill(pid, 15) # SIGTERM
print(f"\n⚠ Client {i} stopped (configuration removed)")
except (ProcessLookupError, OSError):
# Process already dead
pass
# Remove PID file
try:
os.remove(pid_file)
except:
pass
except Exception:
pass
except KeyboardInterrupt:
print("\n\nExiting. Clients still running.")
print("Stop with: pkill -f pyUltroid")
if __name__ == "__main__":
main()