File size: 9,685 Bytes
c723e4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#!/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()