OrbitMC commited on
Commit
a7fec23
·
verified ·
1 Parent(s): 00f2929

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +236 -808
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import os
2
  import sys
3
  import time
@@ -7,739 +8,39 @@ import subprocess
7
  import signal
8
  import socket
9
  import struct
10
- from datetime import datetime, timedelta
11
- from flask import Flask, render_template_string, jsonify, request
 
12
  import logging
13
 
14
  # Disable Flask logging
15
  log = logging.getLogger('werkzeug')
16
  log.setLevel(logging.ERROR)
 
17
  app = Flask(__name__)
 
18
 
19
- # Configuration
20
  SERVER_HOST = "orbitmc.progamer.me"
21
  SERVER_PORT = 40675
22
  SERVER_VERSION = "1.21.1"
23
  BOT_NAMES = ["moderator_1", "moderator_2", "moderator_3", "moderator_4", "moderator_5"]
24
- ROTATION_DURATION = 3600 # 1 hour in seconds
25
 
26
- # Storage
27
  bots = {}
28
  bot_processes = {}
29
  current_bot_index = 0
30
  rotation_start_time = None
31
- server_status = {"online": False, "players": "0/0", "latency": 0, "last_check": None}
32
-
33
- # Modern HTML Interface
34
- HTML = """<!DOCTYPE html>
35
- <html>
36
- <head>
37
- <title>Bot Manager - OrbitMC</title>
38
- <meta charset="utf-8">
39
- <meta name="viewport" content="width=device-width,initial-scale=1">
40
- <style>
41
- * {
42
- margin: 0;
43
- padding: 0;
44
- box-sizing: border-box;
45
- }
46
-
47
- body {
48
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
49
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
50
- min-height: 100vh;
51
- padding: 20px;
52
- color: #fff;
53
- }
54
-
55
- .container {
56
- max-width: 1400px;
57
- margin: 0 auto;
58
- }
59
-
60
- .header {
61
- text-align: center;
62
- margin-bottom: 30px;
63
- }
64
-
65
- .header h1 {
66
- font-size: 42px;
67
- margin-bottom: 10px;
68
- text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
69
- }
70
-
71
- .header p {
72
- font-size: 18px;
73
- opacity: 0.9;
74
- }
75
-
76
- /* Server Status Card */
77
- .server-card {
78
- background: rgba(255, 255, 255, 0.1);
79
- backdrop-filter: blur(10px);
80
- border-radius: 20px;
81
- padding: 30px;
82
- margin-bottom: 25px;
83
- border: 1px solid rgba(255, 255, 255, 0.2);
84
- box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
85
- }
86
-
87
- .server-header {
88
- display: flex;
89
- justify-content: space-between;
90
- align-items: center;
91
- margin-bottom: 20px;
92
- }
93
-
94
- .server-title {
95
- font-size: 24px;
96
- font-weight: bold;
97
- }
98
-
99
- .status-badge {
100
- padding: 8px 20px;
101
- border-radius: 20px;
102
- font-weight: bold;
103
- font-size: 14px;
104
- display: inline-flex;
105
- align-items: center;
106
- gap: 8px;
107
- }
108
-
109
- .status-badge.online {
110
- background: #10b981;
111
- animation: pulse 2s infinite;
112
- }
113
-
114
- .status-badge.offline {
115
- background: #ef4444;
116
- }
117
-
118
- @keyframes pulse {
119
- 0%, 100% { opacity: 1; }
120
- 50% { opacity: 0.7; }
121
- }
122
-
123
- .status-dot {
124
- width: 10px;
125
- height: 10px;
126
- border-radius: 50%;
127
- background: white;
128
- }
129
-
130
- .server-info {
131
- display: grid;
132
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
133
- gap: 15px;
134
- }
135
-
136
- .info-item {
137
- background: rgba(255, 255, 255, 0.1);
138
- padding: 15px;
139
- border-radius: 10px;
140
- text-align: center;
141
- }
142
-
143
- .info-label {
144
- font-size: 12px;
145
- opacity: 0.8;
146
- margin-bottom: 5px;
147
- }
148
-
149
- .info-value {
150
- font-size: 20px;
151
- font-weight: bold;
152
- }
153
-
154
- /* Rotation Card */
155
- .rotation-card {
156
- background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
157
- border-radius: 20px;
158
- padding: 30px;
159
- margin-bottom: 25px;
160
- box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
161
- text-align: center;
162
- }
163
-
164
- .rotation-title {
165
- font-size: 18px;
166
- opacity: 0.9;
167
- margin-bottom: 15px;
168
- }
169
-
170
- .current-bot {
171
- font-size: 48px;
172
- font-weight: bold;
173
- margin: 15px 0;
174
- text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
175
- }
176
-
177
- .rotation-timer {
178
- font-size: 36px;
179
- font-weight: bold;
180
- margin: 20px 0;
181
- font-family: 'Courier New', monospace;
182
- }
183
-
184
- .progress-bar {
185
- background: rgba(255, 255, 255, 0.3);
186
- border-radius: 10px;
187
- height: 20px;
188
- overflow: hidden;
189
- margin: 20px 0;
190
- }
191
-
192
- .progress-fill {
193
- background: rgba(255, 255, 255, 0.9);
194
- height: 100%;
195
- border-radius: 10px;
196
- transition: width 1s linear;
197
- }
198
-
199
- .next-bot-info {
200
- background: rgba(255, 255, 255, 0.2);
201
- padding: 15px;
202
- border-radius: 10px;
203
- margin-top: 15px;
204
- font-size: 16px;
205
- }
206
-
207
- .controls {
208
- display: flex;
209
- justify-content: center;
210
- gap: 10px;
211
- margin-top: 20px;
212
- }
213
-
214
- button {
215
- background: rgba(255, 255, 255, 0.3);
216
- border: 2px solid rgba(255, 255, 255, 0.5);
217
- color: white;
218
- padding: 12px 30px;
219
- border-radius: 25px;
220
- cursor: pointer;
221
- font-size: 16px;
222
- font-weight: bold;
223
- transition: all 0.3s;
224
- }
225
-
226
- button:hover {
227
- background: rgba(255, 255, 255, 0.5);
228
- transform: translateY(-2px);
229
- box-shadow: 0 5px 15px rgba(0,0,0,0.3);
230
- }
231
-
232
- button:active {
233
- transform: translateY(0);
234
- }
235
-
236
- button.primary {
237
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
238
- border: none;
239
- }
240
-
241
- button.primary:hover {
242
- background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
243
- }
244
-
245
- /* Bot Grid */
246
- .bot-grid {
247
- display: grid;
248
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
249
- gap: 20px;
250
- margin-bottom: 30px;
251
- }
252
-
253
- .bot-card {
254
- background: rgba(255, 255, 255, 0.1);
255
- backdrop-filter: blur(10px);
256
- border-radius: 15px;
257
- padding: 20px;
258
- border: 2px solid rgba(255, 255, 255, 0.2);
259
- transition: all 0.3s;
260
- position: relative;
261
- overflow: hidden;
262
- }
263
-
264
- .bot-card::before {
265
- content: '';
266
- position: absolute;
267
- top: 0;
268
- left: 0;
269
- right: 0;
270
- height: 4px;
271
- background: #6b7280;
272
- }
273
-
274
- .bot-card.active::before {
275
- background: linear-gradient(90deg, #f093fb 0%, #f5576c 100%);
276
- animation: shimmer 2s infinite;
277
- }
278
-
279
- .bot-card.online::before {
280
- background: #10b981;
281
- }
282
-
283
- .bot-card.connecting::before {
284
- background: #f59e0b;
285
- }
286
-
287
- .bot-card.offline::before {
288
- background: #6b7280;
289
- }
290
-
291
- @keyframes shimmer {
292
- 0% { transform: translateX(-100%); }
293
- 100% { transform: translateX(100%); }
294
- }
295
-
296
- .bot-card.active {
297
- border-color: rgba(240, 147, 251, 0.5);
298
- background: rgba(240, 147, 251, 0.15);
299
- transform: scale(1.05);
300
- box-shadow: 0 10px 30px rgba(240, 147, 251, 0.3);
301
- }
302
-
303
- .bot-header {
304
- display: flex;
305
- justify-content: space-between;
306
- align-items: center;
307
- margin-bottom: 15px;
308
- }
309
-
310
- .bot-name {
311
- font-size: 20px;
312
- font-weight: bold;
313
- }
314
-
315
- .active-badge {
316
- background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
317
- padding: 4px 12px;
318
- border-radius: 12px;
319
- font-size: 11px;
320
- font-weight: bold;
321
- }
322
-
323
- .bot-stats {
324
- display: grid;
325
- grid-template-columns: 1fr 1fr;
326
- gap: 10px;
327
- margin: 15px 0;
328
- }
329
-
330
- .stat-item {
331
- background: rgba(255, 255, 255, 0.1);
332
- padding: 10px;
333
- border-radius: 8px;
334
- text-align: center;
335
- }
336
-
337
- .stat-label {
338
- font-size: 11px;
339
- opacity: 0.7;
340
- margin-bottom: 3px;
341
- }
342
-
343
- .stat-value {
344
- font-size: 18px;
345
- font-weight: bold;
346
- }
347
-
348
- .bot-status-text {
349
- text-align: center;
350
- padding: 8px;
351
- border-radius: 8px;
352
- font-weight: bold;
353
- margin-top: 10px;
354
- }
355
-
356
- .bot-status-text.online {
357
- background: rgba(16, 185, 129, 0.3);
358
- color: #10b981;
359
- }
360
-
361
- .bot-status-text.offline {
362
- background: rgba(107, 114, 128, 0.3);
363
- color: #9ca3af;
364
- }
365
-
366
- .bot-status-text.connecting {
367
- background: rgba(245, 158, 11, 0.3);
368
- color: #f59e0b;
369
- }
370
-
371
- .bot-status-text.active {
372
- background: rgba(240, 147, 251, 0.3);
373
- color: #f093fb;
374
- }
375
-
376
- /* Queue Section */
377
- .queue-section {
378
- background: rgba(255, 255, 255, 0.1);
379
- backdrop-filter: blur(10px);
380
- border-radius: 20px;
381
- padding: 25px;
382
- margin-bottom: 25px;
383
- border: 1px solid rgba(255, 255, 255, 0.2);
384
- }
385
-
386
- .queue-title {
387
- font-size: 20px;
388
- font-weight: bold;
389
- margin-bottom: 20px;
390
- text-align: center;
391
- }
392
-
393
- .queue-list {
394
- display: flex;
395
- justify-content: center;
396
- align-items: center;
397
- gap: 15px;
398
- flex-wrap: wrap;
399
- }
400
-
401
- .queue-item {
402
- background: rgba(255, 255, 255, 0.1);
403
- padding: 15px 25px;
404
- border-radius: 15px;
405
- font-weight: bold;
406
- transition: all 0.3s;
407
- border: 2px solid transparent;
408
- }
409
-
410
- .queue-item.active {
411
- background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
412
- transform: scale(1.15);
413
- box-shadow: 0 5px 20px rgba(240, 147, 251, 0.4);
414
- }
415
-
416
- .queue-item.next {
417
- border-color: rgba(245, 158, 11, 0.6);
418
- background: rgba(245, 158, 11, 0.2);
419
- }
420
-
421
- .queue-arrow {
422
- font-size: 24px;
423
- opacity: 0.5;
424
  }
425
 
426
- .msg {
427
- position: fixed;
428
- top: 20px;
429
- right: 20px;
430
- background: #10b981;
431
- color: white;
432
- padding: 15px 25px;
433
- border-radius: 10px;
434
- box-shadow: 0 5px 20px rgba(0,0,0,0.3);
435
- z-index: 1000;
436
- animation: slideIn 0.3s;
437
- }
438
-
439
- .msg.error {
440
- background: #ef4444;
441
- }
442
-
443
- @keyframes slideIn {
444
- from {
445
- transform: translateX(400px);
446
- opacity: 0;
447
- }
448
- to {
449
- transform: translateX(0);
450
- opacity: 1;
451
- }
452
- }
453
-
454
- @media (max-width: 768px) {
455
- .header h1 { font-size: 28px; }
456
- .current-bot { font-size: 32px; }
457
- .rotation-timer { font-size: 24px; }
458
- .bot-grid { grid-template-columns: 1fr; }
459
- }
460
- </style>
461
- </head>
462
- <body>
463
- <div class="container">
464
- <div class="header">
465
- <h1>🎮 Minecraft Bot Manager</h1>
466
- <p>OrbitMC Server Monitor & Bot Rotation System</p>
467
- </div>
468
-
469
- <!-- Server Status -->
470
- <div class="server-card">
471
- <div class="server-header">
472
- <div class="server-title">📡 Server Status</div>
473
- <div class="status-badge" id="server-badge">
474
- <div class="status-dot"></div>
475
- <span id="server-status-text">Checking...</span>
476
- </div>
477
- </div>
478
- <div class="server-info">
479
- <div class="info-item">
480
- <div class="info-label">Address</div>
481
- <div class="info-value" id="server-address">-</div>
482
- </div>
483
- <div class="info-item">
484
- <div class="info-label">Players Online</div>
485
- <div class="info-value" id="server-players">-</div>
486
- </div>
487
- <div class="info-item">
488
- <div class="info-label">Latency</div>
489
- <div class="info-value" id="server-latency">-</div>
490
- </div>
491
- <div class="info-item">
492
- <div class="info-label">Version</div>
493
- <div class="info-value" id="server-version">-</div>
494
- </div>
495
- </div>
496
- </div>
497
-
498
- <!-- Current Rotation -->
499
- <div class="rotation-card">
500
- <div class="rotation-title">🔄 CURRENT ACTIVE BOT</div>
501
- <div class="current-bot" id="current-bot">-</div>
502
- <div class="rotation-timer" id="rotation-timer">00:00:00</div>
503
-
504
- <div class="progress-bar">
505
- <div class="progress-fill" id="progress-fill"></div>
506
- </div>
507
-
508
- <div class="next-bot-info">
509
- <strong>Next Bot:</strong> <span id="next-bot">-</span> in <span id="time-until-next">-</span>
510
- </div>
511
-
512
- <div class="controls">
513
- <button class="primary" onclick="forceRotation()">⏭️ Switch to Next Bot</button>
514
- <button onclick="refreshStatus()">🔄 Refresh</button>
515
- </div>
516
- </div>
517
-
518
- <!-- Queue -->
519
- <div class="queue-section">
520
- <div class="queue-title">📋 Rotation Queue</div>
521
- <div class="queue-list" id="queue-list"></div>
522
- </div>
523
-
524
- <!-- Bot Grid -->
525
- <div class="bot-grid" id="bot-grid"></div>
526
-
527
- <div id="msg-container"></div>
528
- </div>
529
-
530
- <script>
531
- let data = {};
532
- let updateTimer;
533
- let serverCheckTimer;
534
-
535
- async function fetchStatus() {
536
- try {
537
- const r = await fetch('/api/status');
538
- data = await r.json();
539
- renderStatus();
540
- } catch(e) {
541
- console.error('Failed to fetch status', e);
542
- }
543
- }
544
-
545
- function renderStatus() {
546
- // Server Status
547
- const server = data.server_status;
548
- const badge = document.getElementById('server-badge');
549
-
550
- if (server.online) {
551
- badge.className = 'status-badge online';
552
- document.getElementById('server-status-text').textContent = 'Online';
553
- } else {
554
- badge.className = 'status-badge offline';
555
- document.getElementById('server-status-text').textContent = 'Offline';
556
- }
557
-
558
- document.getElementById('server-address').textContent = `${data.server_info.host}:${data.server_info.port}`;
559
- document.getElementById('server-players').textContent = server.players;
560
- document.getElementById('server-latency').textContent = server.latency > 0 ? `${server.latency}ms` : 'N/A';
561
- document.getElementById('server-version').textContent = data.server_info.version;
562
-
563
- // Rotation Info
564
- const rotation = data.rotation;
565
- document.getElementById('current-bot').textContent = rotation.current_bot || '-';
566
- document.getElementById('rotation-timer').textContent = formatTime(rotation.elapsed);
567
- document.getElementById('next-bot').textContent = rotation.next_bot || '-';
568
- document.getElementById('time-until-next').textContent = formatTime(rotation.remaining);
569
-
570
- // Progress Bar
571
- const progress = (rotation.elapsed / 3600) * 100;
572
- document.getElementById('progress-fill').style.width = progress + '%';
573
-
574
- // Queue
575
- renderQueue(rotation.queue, rotation.current_bot, rotation.next_bot);
576
-
577
- // Bot Grid
578
- renderBots(data.bots);
579
- }
580
-
581
- function renderQueue(queue, current, next) {
582
- const queueList = document.getElementById('queue-list');
583
- queueList.innerHTML = '';
584
-
585
- queue.forEach((bot, index) => {
586
- const item = document.createElement('div');
587
- item.className = 'queue-item';
588
-
589
- if (bot === current) {
590
- item.className += ' active';
591
- } else if (bot === next) {
592
- item.className += ' next';
593
- }
594
-
595
- item.textContent = bot;
596
- queueList.appendChild(item);
597
-
598
- if (index < queue.length - 1) {
599
- const arrow = document.createElement('div');
600
- arrow.className = 'queue-arrow';
601
- arrow.textContent = '→';
602
- queueList.appendChild(arrow);
603
- }
604
- });
605
- }
606
-
607
- function renderBots(bots) {
608
- const grid = document.getElementById('bot-grid');
609
- grid.innerHTML = '';
610
-
611
- bots.forEach(bot => {
612
- const card = document.createElement('div');
613
- card.className = `bot-card ${bot.status}`;
614
-
615
- if (bot.is_active) {
616
- card.className += ' active';
617
- }
618
-
619
- const activeBadge = bot.is_active ? '<div class="active-badge">⚡ ACTIVE NOW</div>' : '';
620
-
621
- const statusClass = bot.is_active ? 'active' : bot.status;
622
- const statusText = bot.is_active ? '⚡ ACTIVE & ONLINE' : bot.status.toUpperCase();
623
-
624
- card.innerHTML = `
625
- <div class="bot-header">
626
- <div class="bot-name">${bot.name}</div>
627
- ${activeBadge}
628
- </div>
629
- <div class="bot-stats">
630
- <div class="stat-item">
631
- <div class="stat-label">Uptime</div>
632
- <div class="stat-value">${formatTimeShort(bot.uptime)}</div>
633
- </div>
634
- <div class="stat-item">
635
- <div class="stat-label">Deaths</div>
636
- <div class="stat-value">${bot.deaths}</div>
637
- </div>
638
- <div class="stat-item">
639
- <div class="stat-label">Reconnects</div>
640
- <div class="stat-value">${bot.reconnects}</div>
641
- </div>
642
- <div class="stat-item">
643
- <div class="stat-label">Position</div>
644
- <div class="stat-value">${bot.position}</div>
645
- </div>
646
- </div>
647
- <div class="bot-status-text ${statusClass}">${statusText}</div>
648
- `;
649
-
650
- grid.appendChild(card);
651
- });
652
- }
653
-
654
- function formatTime(seconds) {
655
- if (!seconds || seconds < 0) return '00:00:00';
656
- const h = Math.floor(seconds / 3600);
657
- const m = Math.floor((seconds % 3600) / 60);
658
- const s = seconds % 60;
659
- return `${h.toString().padStart(2,'0')}:${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;
660
- }
661
-
662
- function formatTimeShort(seconds) {
663
- if (!seconds || seconds < 0) return '0m';
664
- const h = Math.floor(seconds / 3600);
665
- const m = Math.floor((seconds % 3600) / 60);
666
- if (h > 0) return `${h}h ${m}m`;
667
- return `${m}m`;
668
- }
669
-
670
- async function forceRotation() {
671
- if (!confirm('Switch to the next bot now?')) return;
672
-
673
- try {
674
- const r = await fetch('/api/next_rotation', {method: 'POST'});
675
- const res = await r.json();
676
-
677
- if (res.success) {
678
- showMsg('✅ Switching to next bot...', 'success');
679
- setTimeout(fetchStatus, 1000);
680
- } else {
681
- showMsg('❌ ' + (res.error || 'Failed to rotate'), 'error');
682
- }
683
- } catch(e) {
684
- showMsg('❌ Failed to switch bot', 'error');
685
- }
686
- }
687
-
688
- function refreshStatus() {
689
- fetchStatus();
690
- showMsg('🔄 Refreshing...', 'success');
691
- }
692
-
693
- function showMsg(text, type = 'success') {
694
- const container = document.getElementById('msg-container');
695
- const msg = document.createElement('div');
696
- msg.className = 'msg' + (type === 'error' ? ' error' : '');
697
- msg.textContent = text;
698
- container.appendChild(msg);
699
-
700
- setTimeout(() => {
701
- msg.remove();
702
- }, 3000);
703
- }
704
-
705
- function startAutoUpdate() {
706
- if (updateTimer) clearInterval(updateTimer);
707
- if (serverCheckTimer) clearInterval(serverCheckTimer);
708
-
709
- // Update full status every 2 seconds
710
- updateTimer = setInterval(fetchStatus, 2000);
711
-
712
- // Update server status every 1 second
713
- serverCheckTimer = setInterval(async () => {
714
- try {
715
- const r = await fetch('/api/server_status');
716
- const server = await r.json();
717
-
718
- const badge = document.getElementById('server-badge');
719
- if (server.online) {
720
- badge.className = 'status-badge online';
721
- document.getElementById('server-status-text').textContent = 'Online';
722
- } else {
723
- badge.className = 'status-badge offline';
724
- document.getElementById('server-status-text').textContent = 'Offline';
725
- }
726
-
727
- document.getElementById('server-players').textContent = server.players;
728
- document.getElementById('server-latency').textContent = server.latency > 0 ? `${server.latency}ms` : 'N/A';
729
- } catch(e) {
730
- console.error('Server check failed', e);
731
- }
732
- }, 1000);
733
- }
734
-
735
- // Initialize
736
- fetchStatus();
737
- startAutoUpdate();
738
- </script>
739
- </body>
740
- </html>"""
741
-
742
- # Bot Node.js script with circular movement
743
  BOT_SCRIPT = """
744
  const mineflayer = require('mineflayer');
745
  const { Vec3 } = require('vec3');
@@ -749,13 +50,14 @@ const host = process.argv[3];
749
  const port = parseInt(process.argv[4]) || 25565;
750
  const version = process.argv[5] || false;
751
 
752
- console.log(JSON.stringify({event:'starting',name:botName}));
753
-
754
- let reconnectAttempts = 0;
755
  let isConnected = false;
756
  let circleInterval = null;
 
757
  let angle = 0;
758
  let centerPos = null;
 
 
 
759
 
760
  function createBot() {
761
  const bot = mineflayer.createBot({
@@ -773,56 +75,57 @@ function createBot() {
773
  function startCircularMovement() {
774
  if (circleInterval) clearInterval(circleInterval);
775
 
776
- // Wait a bit for spawn
777
  setTimeout(() => {
778
  if (!bot.entity || !isConnected) return;
779
 
780
- // Set center position
781
  centerPos = bot.entity.position.clone();
782
  angle = 0;
783
 
784
- console.log(JSON.stringify({event:'movement_started',name:botName,center:centerPos}));
 
 
 
 
 
 
 
 
785
 
786
- // Move in circle every 100ms
787
  circleInterval = setInterval(() => {
788
  if (!bot.entity || !isConnected || !centerPos) return;
789
 
790
  try {
791
- const radius = 3; // 3 block radius circle
792
- angle += 0.05; // Rotation speed
793
 
794
- // Calculate target position
795
  const targetX = centerPos.x + Math.cos(angle) * radius;
796
  const targetZ = centerPos.z + Math.sin(angle) * radius;
797
- const targetY = centerPos.y;
798
 
799
- const target = new Vec3(targetX, targetY, targetZ);
800
-
801
- // Look at target
802
- const dx = target.x - bot.entity.position.x;
803
- const dz = target.z - bot.entity.position.z;
804
  const yaw = Math.atan2(-dx, -dz);
805
- bot.look(yaw, 0, true);
806
 
807
- // Move forward
808
  bot.setControlState('forward', true);
809
 
810
- // Send position update
811
- if (Math.floor(angle * 10) % 10 === 0) {
812
  const pos = bot.entity.position;
813
  console.log(JSON.stringify({
814
  event:'position',
815
  name:botName,
816
  x: Math.floor(pos.x),
817
  y: Math.floor(pos.y),
818
- z: Math.floor(pos.z)
 
 
819
  }));
820
  }
821
  } catch(err) {
822
- // Ignore movement errors
823
  }
824
  }, 100);
825
- }, 2000);
826
  }
827
 
828
  function stopMovement() {
@@ -830,74 +133,124 @@ function createBot() {
830
  clearInterval(circleInterval);
831
  circleInterval = null;
832
  }
 
 
 
 
833
  try {
834
  bot.setControlState('forward', false);
835
- bot.setControlState('back', false);
836
- bot.setControlState('left', false);
837
- bot.setControlState('right', false);
838
  } catch(err) {}
839
  }
840
 
841
  bot.once('spawn', () => {
842
- console.log(JSON.stringify({event:'connected',name:botName}));
 
 
 
 
 
 
843
  isConnected = true;
844
- reconnectAttempts = 0;
845
  startCircularMovement();
 
 
 
 
 
 
 
 
 
 
846
  });
847
 
848
  bot.on('respawn', () => {
849
- console.log(JSON.stringify({event:'respawned',name:botName}));
850
  stopMovement();
851
  centerPos = null;
852
  startCircularMovement();
853
  });
854
 
855
  bot.on('death', () => {
856
- console.log(JSON.stringify({event:'death',name:botName}));
857
  stopMovement();
858
  centerPos = null;
859
  });
860
 
 
 
 
 
 
 
861
  bot.on('kicked', (reason) => {
862
- console.log(JSON.stringify({event:'kicked',name:botName,reason:reason}));
 
 
 
 
 
863
  isConnected = false;
864
  stopMovement();
865
  });
866
 
867
  bot.on('error', (err) => {
868
- console.log(JSON.stringify({event:'error',name:botName,error:err.message}));
 
 
 
 
 
869
  });
870
 
871
  bot.on('end', (reason) => {
872
- console.log(JSON.stringify({event:'disconnected',name:botName}));
 
 
 
 
 
873
  isConnected = false;
874
  stopMovement();
875
 
876
- // Auto reconnect
877
- reconnectAttempts++;
878
- console.log(JSON.stringify({event:'reconnecting',name:botName,attempt:reconnectAttempts}));
879
- setTimeout(() => {
880
  createBot();
881
  }, 5000);
882
  });
883
 
 
 
 
 
 
 
 
884
  // Heartbeat
885
  setInterval(() => {
886
- if (isConnected) {
887
- console.log(JSON.stringify({event:'alive',name:botName}));
 
 
 
 
 
 
888
  }
889
  }, 30000);
890
 
891
  process.on('SIGTERM', () => {
892
  stopMovement();
893
  bot.quit();
894
- process.exit();
895
  });
896
 
897
  process.on('SIGINT', () => {
898
  stopMovement();
899
  bot.quit();
900
- process.exit();
901
  });
902
  }
903
 
@@ -905,38 +258,89 @@ createBot();
905
  """
906
 
907
  def write_bot_script():
908
- """Write bot script to temp directory"""
909
- path = '/tmp/bot.js'
910
  try:
911
- with open(path, 'w') as f:
912
  f.write(BOT_SCRIPT)
 
913
  return True
914
  except Exception as e:
915
- print(f"Error writing bot script: {e}")
916
  return False
917
 
918
- def check_server_status():
919
- """Check Minecraft server status with latency"""
920
  global server_status
 
921
  try:
922
  start_time = time.time()
923
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
924
- sock.settimeout(3)
925
- result = sock.connect_ex((SERVER_HOST, SERVER_PORT))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
926
  latency = int((time.time() - start_time) * 1000)
927
- sock.close()
928
 
929
- server_status["online"] = result == 0
930
- server_status["latency"] = latency if result == 0 else 0
931
- server_status["last_check"] = datetime.now()
932
 
933
- if not server_status["online"]:
934
- server_status["players"] = "N/A"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
935
  except Exception as e:
936
- server_status["online"] = False
937
- server_status["players"] = "N/A"
938
- server_status["latency"] = 0
939
- server_status["last_check"] = datetime.now()
 
 
 
940
 
941
  def start_bot(name):
942
  """Start a bot process"""
@@ -965,8 +369,9 @@ def start_bot(name):
965
  'start_time': time.time(),
966
  'deaths': 0,
967
  'reconnects': 0,
968
- 'uptime': 0,
969
- 'position': '0, 0, 0'
 
970
  }
971
  else:
972
  bots[name]['status'] = 'connecting'
@@ -974,14 +379,14 @@ def start_bot(name):
974
 
975
  threading.Thread(target=monitor_bot, args=(name,), daemon=True).start()
976
 
977
- print(f" Started bot: {name}")
978
  return True
979
  except Exception as e:
980
  print(f"❌ Error starting bot {name}: {e}")
981
  return False
982
 
983
  def stop_bot(name):
984
- """Stop a bot process"""
985
  if name in bot_processes:
986
  try:
987
  proc = bot_processes[name]
@@ -992,10 +397,10 @@ def stop_bot(name):
992
  bots[name]['status'] = 'offline'
993
  print(f"⏹️ Stopped bot: {name}")
994
  except Exception as e:
995
- print(f"Error stopping bot {name}: {e}")
996
 
997
  def monitor_bot(name):
998
- """Monitor bot process output"""
999
  if name not in bot_processes:
1000
  return
1001
 
@@ -1012,18 +417,25 @@ def monitor_bot(name):
1012
  event = data.get('event')
1013
 
1014
  if name in bots:
1015
- if event == 'connected' or event == 'alive':
1016
  bots[name]['status'] = 'online'
1017
- elif event == 'death':
 
 
 
 
 
 
 
 
 
 
1018
  bots[name]['deaths'] += 1
1019
  elif event == 'reconnecting':
1020
  bots[name]['reconnects'] += 1
1021
  bots[name]['status'] = 'connecting'
1022
- elif event in ['disconnected', 'error']:
1023
  bots[name]['status'] = 'offline'
1024
- elif event == 'position':
1025
- x, y, z = data.get('x', 0), data.get('y', 0), data.get('z', 0)
1026
- bots[name]['position'] = f"{x}, {y}, {z}"
1027
 
1028
  if event in ['connected', 'disconnected', 'kicked', 'error', 'death']:
1029
  print(f"[{name}] {event}")
@@ -1049,48 +461,54 @@ def rotation_manager():
1049
  for name in BOT_NAMES:
1050
  stop_bot(name)
1051
 
1052
- time.sleep(2)
1053
 
1054
  # Start new bot
1055
  start_bot(current_bot)
1056
  rotation_start_time = time.time()
1057
- print(f"🔄 Rotation: Now active -> {current_bot}")
1058
 
1059
- # Move to next bot for next rotation
1060
  current_bot_index = (current_bot_index + 1) % len(BOT_NAMES)
1061
 
1062
  time.sleep(5)
1063
  except Exception as e:
1064
- print(f"Rotation error: {e}")
1065
  time.sleep(10)
1066
 
1067
- def initialize_bots():
1068
- """Initialize all bots in storage"""
 
 
 
 
 
 
 
 
 
 
1069
  for name in BOT_NAMES:
1070
  bots[name] = {
1071
  'status': 'offline',
1072
  'start_time': 0,
1073
  'deaths': 0,
1074
  'reconnects': 0,
1075
- 'uptime': 0,
1076
- 'position': '0, 0, 0'
 
1077
  }
1078
 
 
 
1079
  @app.route('/')
1080
  def index():
1081
- return HTML
1082
-
1083
- @app.route('/api/server_status')
1084
- def api_server_status():
1085
- """Get only server status (called every 1s)"""
1086
- check_server_status()
1087
- return jsonify(server_status)
1088
 
1089
  @app.route('/api/status')
1090
  def api_status():
1091
- """Get complete status"""
1092
- check_server_status()
1093
-
1094
  current_bot = BOT_NAMES[(current_bot_index - 1) % len(BOT_NAMES)] if rotation_start_time else BOT_NAMES[current_bot_index]
1095
  next_bot = BOT_NAMES[current_bot_index]
1096
 
@@ -1109,7 +527,9 @@ def api_status():
1109
  'uptime': uptime,
1110
  'deaths': bot.get('deaths', 0),
1111
  'reconnects': bot.get('reconnects', 0),
1112
- 'position': bot.get('position', '0, 0, 0')
 
 
1113
  })
1114
 
1115
  return jsonify({
@@ -1147,18 +567,26 @@ if __name__ == '__main__':
1147
  signal.signal(signal.SIGINT, cleanup)
1148
  signal.signal(signal.SIGTERM, cleanup)
1149
 
 
 
 
 
1150
  if not write_bot_script():
1151
  print("❌ Failed to write bot script!")
1152
  sys.exit(1)
1153
 
1154
- print("🚀 Bot Manager Starting...")
1155
  print(f"📡 Server: {SERVER_HOST}:{SERVER_PORT}")
1156
  print(f"🤖 Bots: {', '.join(BOT_NAMES)}")
1157
- print(f"⏱️ Rotation: {ROTATION_DURATION//60} minutes per bot")
 
1158
 
1159
- initialize_bots()
1160
 
 
1161
  threading.Thread(target=rotation_manager, daemon=True).start()
 
1162
 
1163
  print("🌐 Dashboard: http://localhost:7860")
 
 
1164
  app.run(host='0.0.0.0', port=7860, debug=False, use_reloader=False)
 
1
+ # app.py
2
  import os
3
  import sys
4
  import time
 
8
  import signal
9
  import socket
10
  import struct
11
+ from datetime import datetime
12
+ from flask import Flask, jsonify, request
13
+ from flask_cors import CORS
14
  import logging
15
 
16
  # Disable Flask logging
17
  log = logging.getLogger('werkzeug')
18
  log.setLevel(logging.ERROR)
19
+
20
  app = Flask(__name__)
21
+ CORS(app)
22
 
23
+ # ==================== CONFIGURATION ====================
24
  SERVER_HOST = "orbitmc.progamer.me"
25
  SERVER_PORT = 40675
26
  SERVER_VERSION = "1.21.1"
27
  BOT_NAMES = ["moderator_1", "moderator_2", "moderator_3", "moderator_4", "moderator_5"]
28
+ ROTATION_DURATION = 3600 # 1 hour
29
 
30
+ # ==================== STORAGE ====================
31
  bots = {}
32
  bot_processes = {}
33
  current_bot_index = 0
34
  rotation_start_time = None
35
+ server_status = {
36
+ "online": False,
37
+ "players": "0/0",
38
+ "latency": 0,
39
+ "last_check": None,
40
+ "motd": ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
 
43
+ # ==================== BOT SCRIPT ====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  BOT_SCRIPT = """
45
  const mineflayer = require('mineflayer');
46
  const { Vec3 } = require('vec3');
 
50
  const port = parseInt(process.argv[4]) || 25565;
51
  const version = process.argv[5] || false;
52
 
 
 
 
53
  let isConnected = false;
54
  let circleInterval = null;
55
+ let chatInterval = null;
56
  let angle = 0;
57
  let centerPos = null;
58
+ let reconnectTimeout = null;
59
+
60
+ console.log(JSON.stringify({event:'starting', name:botName, time:Date.now()}));
61
 
62
  function createBot() {
63
  const bot = mineflayer.createBot({
 
75
  function startCircularMovement() {
76
  if (circleInterval) clearInterval(circleInterval);
77
 
 
78
  setTimeout(() => {
79
  if (!bot.entity || !isConnected) return;
80
 
 
81
  centerPos = bot.entity.position.clone();
82
  angle = 0;
83
 
84
+ console.log(JSON.stringify({
85
+ event:'movement_started',
86
+ name:botName,
87
+ center: {
88
+ x: Math.floor(centerPos.x),
89
+ y: Math.floor(centerPos.y),
90
+ z: Math.floor(centerPos.z)
91
+ }
92
+ }));
93
 
 
94
  circleInterval = setInterval(() => {
95
  if (!bot.entity || !isConnected || !centerPos) return;
96
 
97
  try {
98
+ const radius = 4;
99
+ angle += 0.03;
100
 
 
101
  const targetX = centerPos.x + Math.cos(angle) * radius;
102
  const targetZ = centerPos.z + Math.sin(angle) * radius;
 
103
 
104
+ const dx = targetX - bot.entity.position.x;
105
+ const dz = targetZ - bot.entity.position.z;
 
 
 
106
  const yaw = Math.atan2(-dx, -dz);
 
107
 
108
+ bot.look(yaw, 0, true);
109
  bot.setControlState('forward', true);
110
 
111
+ // Send position every 2 seconds
112
+ if (Math.floor(angle * 100) % 200 === 0) {
113
  const pos = bot.entity.position;
114
  console.log(JSON.stringify({
115
  event:'position',
116
  name:botName,
117
  x: Math.floor(pos.x),
118
  y: Math.floor(pos.y),
119
+ z: Math.floor(pos.z),
120
+ health: bot.health,
121
+ food: bot.food
122
  }));
123
  }
124
  } catch(err) {
125
+ console.log(JSON.stringify({event:'movement_error', name:botName, error:err.message}));
126
  }
127
  }, 100);
128
+ }, 3000);
129
  }
130
 
131
  function stopMovement() {
 
133
  clearInterval(circleInterval);
134
  circleInterval = null;
135
  }
136
+ if (chatInterval) {
137
+ clearInterval(chatInterval);
138
+ chatInterval = null;
139
+ }
140
  try {
141
  bot.setControlState('forward', false);
 
 
 
142
  } catch(err) {}
143
  }
144
 
145
  bot.once('spawn', () => {
146
+ console.log(JSON.stringify({
147
+ event:'connected',
148
+ name:botName,
149
+ time:Date.now(),
150
+ gamemode: bot.game.gameMode,
151
+ dimension: bot.game.dimension
152
+ }));
153
  isConnected = true;
 
154
  startCircularMovement();
155
+
156
+ // Random chat
157
+ chatInterval = setInterval(() => {
158
+ if (isConnected && Math.random() > 0.8) {
159
+ try {
160
+ const messages = ['Hello!', 'Im monitoring', 'All good here', ':)', 'Server looking good'];
161
+ bot.chat(messages[Math.floor(Math.random() * messages.length)]);
162
+ } catch(err) {}
163
+ }
164
+ }, 180000); // Every 3 minutes
165
  });
166
 
167
  bot.on('respawn', () => {
168
+ console.log(JSON.stringify({event:'respawned', name:botName, time:Date.now()}));
169
  stopMovement();
170
  centerPos = null;
171
  startCircularMovement();
172
  });
173
 
174
  bot.on('death', () => {
175
+ console.log(JSON.stringify({event:'death', name:botName, time:Date.now()}));
176
  stopMovement();
177
  centerPos = null;
178
  });
179
 
180
+ bot.on('health', () => {
181
+ if (bot.health <= 0) {
182
+ console.log(JSON.stringify({event:'died', name:botName}));
183
+ }
184
+ });
185
+
186
  bot.on('kicked', (reason) => {
187
+ console.log(JSON.stringify({
188
+ event:'kicked',
189
+ name:botName,
190
+ reason: JSON.stringify(reason),
191
+ time:Date.now()
192
+ }));
193
  isConnected = false;
194
  stopMovement();
195
  });
196
 
197
  bot.on('error', (err) => {
198
+ console.log(JSON.stringify({
199
+ event:'error',
200
+ name:botName,
201
+ error:err.message,
202
+ time:Date.now()
203
+ }));
204
  });
205
 
206
  bot.on('end', (reason) => {
207
+ console.log(JSON.stringify({
208
+ event:'disconnected',
209
+ name:botName,
210
+ reason: reason || 'unknown',
211
+ time:Date.now()
212
+ }));
213
  isConnected = false;
214
  stopMovement();
215
 
216
+ // Auto reconnect after 5 seconds
217
+ if (reconnectTimeout) clearTimeout(reconnectTimeout);
218
+ reconnectTimeout = setTimeout(() => {
219
+ console.log(JSON.stringify({event:'reconnecting', name:botName}));
220
  createBot();
221
  }, 5000);
222
  });
223
 
224
+ bot.on('message', (message) => {
225
+ const msg = message.toString();
226
+ if (msg.includes(botName)) {
227
+ console.log(JSON.stringify({event:'mentioned', name:botName, message:msg}));
228
+ }
229
+ });
230
+
231
  // Heartbeat
232
  setInterval(() => {
233
+ if (isConnected && bot.entity) {
234
+ console.log(JSON.stringify({
235
+ event:'heartbeat',
236
+ name:botName,
237
+ health: bot.health || 0,
238
+ food: bot.food || 0,
239
+ time:Date.now()
240
+ }));
241
  }
242
  }, 30000);
243
 
244
  process.on('SIGTERM', () => {
245
  stopMovement();
246
  bot.quit();
247
+ process.exit(0);
248
  });
249
 
250
  process.on('SIGINT', () => {
251
  stopMovement();
252
  bot.quit();
253
+ process.exit(0);
254
  });
255
  }
256
 
 
258
  """
259
 
260
  def write_bot_script():
261
+ """Write bot.js to disk"""
 
262
  try:
263
+ with open('/tmp/bot.js', 'w') as f:
264
  f.write(BOT_SCRIPT)
265
+ print("✅ Bot script written to /tmp/bot.js")
266
  return True
267
  except Exception as e:
268
+ print(f" Failed to write bot script: {e}")
269
  return False
270
 
271
+ def ping_minecraft_server():
272
+ """Ping Minecraft server and get real status"""
273
  global server_status
274
+
275
  try:
276
  start_time = time.time()
277
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
278
+ sock.settimeout(5)
279
+
280
+ # Connect
281
+ sock.connect((SERVER_HOST, SERVER_PORT))
282
+
283
+ # Handshake packet
284
+ handshake = b'\x00\x00' + len(SERVER_HOST).to_bytes(1, 'big') + SERVER_HOST.encode('utf-8')
285
+ handshake += SERVER_PORT.to_bytes(2, 'big') + b'\x01'
286
+ handshake = len(handshake).to_bytes(1, 'big') + handshake
287
+
288
+ # Status request
289
+ status_request = b'\x01\x00'
290
+
291
+ sock.send(handshake)
292
+ sock.send(status_request)
293
+
294
+ # Read response
295
+ response_length = sock.recv(1024)
296
+ response_data = sock.recv(4096)
297
+
298
  latency = int((time.time() - start_time) * 1000)
 
299
 
300
+ sock.close()
 
 
301
 
302
+ # Parse JSON response
303
+ try:
304
+ # Skip packet ID
305
+ json_start = response_data.find(b'{')
306
+ if json_start != -1:
307
+ json_data = response_data[json_start:].decode('utf-8', errors='ignore')
308
+ server_info = json.loads(json_data)
309
+
310
+ players = server_info.get('players', {})
311
+ online = players.get('online', 0)
312
+ max_players = players.get('max', 0)
313
+
314
+ server_status.update({
315
+ "online": True,
316
+ "players": f"{online}/{max_players}",
317
+ "latency": latency,
318
+ "motd": server_info.get('description', {}).get('text', 'Minecraft Server'),
319
+ "last_check": datetime.now().isoformat()
320
+ })
321
+ else:
322
+ server_status.update({
323
+ "online": True,
324
+ "players": "?/?",
325
+ "latency": latency,
326
+ "last_check": datetime.now().isoformat()
327
+ })
328
+ except:
329
+ server_status.update({
330
+ "online": True,
331
+ "players": "?/?",
332
+ "latency": latency,
333
+ "last_check": datetime.now().isoformat()
334
+ })
335
+
336
  except Exception as e:
337
+ server_status.update({
338
+ "online": False,
339
+ "players": "0/0",
340
+ "latency": 0,
341
+ "motd": str(e),
342
+ "last_check": datetime.now().isoformat()
343
+ })
344
 
345
  def start_bot(name):
346
  """Start a bot process"""
 
369
  'start_time': time.time(),
370
  'deaths': 0,
371
  'reconnects': 0,
372
+ 'position': {'x': 0, 'y': 0, 'z': 0},
373
+ 'health': 20,
374
+ 'food': 20
375
  }
376
  else:
377
  bots[name]['status'] = 'connecting'
 
379
 
380
  threading.Thread(target=monitor_bot, args=(name,), daemon=True).start()
381
 
382
+ print(f"🚀 Started bot: {name}")
383
  return True
384
  except Exception as e:
385
  print(f"❌ Error starting bot {name}: {e}")
386
  return False
387
 
388
  def stop_bot(name):
389
+ """Stop a bot"""
390
  if name in bot_processes:
391
  try:
392
  proc = bot_processes[name]
 
397
  bots[name]['status'] = 'offline'
398
  print(f"⏹️ Stopped bot: {name}")
399
  except Exception as e:
400
+ print(f"⚠️ Error stopping bot {name}: {e}")
401
 
402
  def monitor_bot(name):
403
+ """Monitor bot output"""
404
  if name not in bot_processes:
405
  return
406
 
 
417
  event = data.get('event')
418
 
419
  if name in bots:
420
+ if event in ['connected', 'heartbeat']:
421
  bots[name]['status'] = 'online'
422
+ bots[name]['health'] = data.get('health', 20)
423
+ bots[name]['food'] = data.get('food', 20)
424
+ elif event == 'position':
425
+ bots[name]['position'] = {
426
+ 'x': data.get('x', 0),
427
+ 'y': data.get('y', 0),
428
+ 'z': data.get('z', 0)
429
+ }
430
+ bots[name]['health'] = data.get('health', 20)
431
+ bots[name]['food'] = data.get('food', 20)
432
+ elif event in ['death', 'died']:
433
  bots[name]['deaths'] += 1
434
  elif event == 'reconnecting':
435
  bots[name]['reconnects'] += 1
436
  bots[name]['status'] = 'connecting'
437
+ elif event in ['disconnected', 'error', 'kicked']:
438
  bots[name]['status'] = 'offline'
 
 
 
439
 
440
  if event in ['connected', 'disconnected', 'kicked', 'error', 'death']:
441
  print(f"[{name}] {event}")
 
461
  for name in BOT_NAMES:
462
  stop_bot(name)
463
 
464
+ time.sleep(3)
465
 
466
  # Start new bot
467
  start_bot(current_bot)
468
  rotation_start_time = time.time()
469
+ print(f"🔄 Rotation: {current_bot} is now active")
470
 
471
+ # Next bot
472
  current_bot_index = (current_bot_index + 1) % len(BOT_NAMES)
473
 
474
  time.sleep(5)
475
  except Exception as e:
476
+ print(f"⚠️ Rotation error: {e}")
477
  time.sleep(10)
478
 
479
+ def server_ping_loop():
480
+ """Continuously ping server"""
481
+ while True:
482
+ try:
483
+ ping_minecraft_server()
484
+ time.sleep(1)
485
+ except Exception as e:
486
+ print(f"⚠️ Server ping error: {e}")
487
+ time.sleep(2)
488
+
489
+ def initialize():
490
+ """Initialize bots"""
491
  for name in BOT_NAMES:
492
  bots[name] = {
493
  'status': 'offline',
494
  'start_time': 0,
495
  'deaths': 0,
496
  'reconnects': 0,
497
+ 'position': {'x': 0, 'y': 0, 'z': 0},
498
+ 'health': 20,
499
+ 'food': 20
500
  }
501
 
502
+ # ==================== API ROUTES ====================
503
+
504
  @app.route('/')
505
  def index():
506
+ """Serve HTML"""
507
+ return open('index.html').read()
 
 
 
 
 
508
 
509
  @app.route('/api/status')
510
  def api_status():
511
+ """Get full status"""
 
 
512
  current_bot = BOT_NAMES[(current_bot_index - 1) % len(BOT_NAMES)] if rotation_start_time else BOT_NAMES[current_bot_index]
513
  next_bot = BOT_NAMES[current_bot_index]
514
 
 
527
  'uptime': uptime,
528
  'deaths': bot.get('deaths', 0),
529
  'reconnects': bot.get('reconnects', 0),
530
+ 'position': bot.get('position', {'x': 0, 'y': 0, 'z': 0}),
531
+ 'health': bot.get('health', 20),
532
+ 'food': bot.get('food', 20)
533
  })
534
 
535
  return jsonify({
 
567
  signal.signal(signal.SIGINT, cleanup)
568
  signal.signal(signal.SIGTERM, cleanup)
569
 
570
+ print("=" * 60)
571
+ print("🎮 MINECRAFT BOT MANAGER")
572
+ print("=" * 60)
573
+
574
  if not write_bot_script():
575
  print("❌ Failed to write bot script!")
576
  sys.exit(1)
577
 
 
578
  print(f"📡 Server: {SERVER_HOST}:{SERVER_PORT}")
579
  print(f"🤖 Bots: {', '.join(BOT_NAMES)}")
580
+ print(f"⏱️ Rotation: {ROTATION_DURATION//60} minutes per bot")
581
+ print("=" * 60)
582
 
583
+ initialize()
584
 
585
+ # Start threads
586
  threading.Thread(target=rotation_manager, daemon=True).start()
587
+ threading.Thread(target=server_ping_loop, daemon=True).start()
588
 
589
  print("🌐 Dashboard: http://localhost:7860")
590
+ print("=" * 60)
591
+
592
  app.run(host='0.0.0.0', port=7860, debug=False, use_reloader=False)