Tokipo commited on
Commit
9c6e5f5
·
verified ·
1 Parent(s): ad8831a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +238 -95
app.py CHANGED
@@ -3,17 +3,19 @@ import json
3
  import time
4
  import threading
5
  import requests
6
- import pandas as pd
7
  from flask import Flask, render_template_string, jsonify, request
8
  from datetime import datetime, timedelta
9
  import subprocess
10
- import socket
 
 
 
11
 
12
  app = Flask(__name__)
13
 
 
14
  SHEET_ID = os.environ.get("SHEET_ID")
15
  SHEET_URL = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/export?format=csv"
16
-
17
  # Bot storage
18
  bots = {}
19
  bot_processes = {}
@@ -38,36 +40,61 @@ const bot = mineflayer.createBot({
38
  username: botName,
39
  version: version,
40
  hideErrors: true,
41
- checkTimeoutInterval: 60000
 
42
  });
43
 
 
 
44
  bot.on('spawn', () => {
45
  console.log('CONNECTED');
46
- setInterval(() => {
47
- // Random movement for anti-AFK
 
48
  const actions = [
49
- () => bot.setControlState('forward', true),
50
- () => bot.setControlState('back', true),
51
- () => bot.setControlState('left', true),
52
- () => bot.setControlState('right', true),
53
- () => bot.setControlState('jump', true)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  ];
55
 
56
  const randomAction = actions[Math.floor(Math.random() * actions.length)];
57
  randomAction();
58
-
59
- setTimeout(() => {
60
- bot.clearControlStates();
61
- }, 100);
62
  }, 30000); // Move every 30 seconds
63
  });
64
 
65
  bot.on('death', () => {
66
  console.log('DIED');
 
 
 
 
 
67
  });
68
 
69
  bot.on('kicked', (reason) => {
70
  console.log('KICKED:', reason);
 
71
  });
72
 
73
  bot.on('error', (err) => {
@@ -76,6 +103,14 @@ bot.on('error', (err) => {
76
 
77
  bot.on('end', () => {
78
  console.log('DISCONNECTED');
 
 
 
 
 
 
 
 
79
  process.exit();
80
  });
81
  """
@@ -85,6 +120,7 @@ HTML_TEMPLATE = """
85
  <html>
86
  <head>
87
  <title>Mineflayer Bot Manager</title>
 
88
  <style>
89
  * {
90
  margin: 0;
@@ -101,11 +137,18 @@ HTML_TEMPLATE = """
101
  max-width: 1400px;
102
  margin: 0 auto;
103
  }
104
- h1 {
105
- color: white;
106
  text-align: center;
107
  margin-bottom: 30px;
 
 
 
108
  text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
 
 
 
 
 
109
  }
110
  .stats {
111
  display: flex;
@@ -120,19 +163,44 @@ HTML_TEMPLATE = """
120
  flex: 1;
121
  min-width: 200px;
122
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
 
123
  }
124
  .stat-card h3 {
125
  color: #667eea;
126
  margin-bottom: 10px;
 
 
 
127
  }
128
  .stat-card .number {
129
  font-size: 2em;
130
  font-weight: bold;
131
  color: #333;
132
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  .bot-grid {
134
  display: grid;
135
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
136
  gap: 20px;
137
  }
138
  .bot-card {
@@ -140,86 +208,103 @@ HTML_TEMPLATE = """
140
  border-radius: 10px;
141
  padding: 20px;
142
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
143
- transition: transform 0.3s;
 
144
  }
145
  .bot-card:hover {
146
  transform: translateY(-5px);
147
- box-shadow: 0 6px 12px rgba(0,0,0,0.15);
148
  }
149
  .bot-name {
150
- font-size: 1.2em;
151
  font-weight: bold;
152
  color: #333;
153
  margin-bottom: 10px;
 
 
 
154
  }
155
  .bot-info {
156
  color: #666;
157
  margin: 5px 0;
 
158
  }
159
  .status {
160
  display: inline-block;
161
- padding: 5px 10px;
162
  border-radius: 20px;
163
- font-size: 0.9em;
164
  font-weight: bold;
165
  margin: 10px 0;
 
 
166
  }
167
  .status.connected {
168
- background: #10b981;
169
  color: white;
170
  }
171
  .status.disconnected {
172
- background: #ef4444;
173
  color: white;
174
  }
175
  .status.connecting {
176
- background: #f59e0b;
177
  color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  }
179
  .btn {
180
- padding: 10px 20px;
181
  border: none;
182
  border-radius: 5px;
183
  cursor: pointer;
184
  font-weight: bold;
185
  transition: all 0.3s;
186
  margin-top: 10px;
 
187
  }
188
  .btn-rejoin {
189
- background: #667eea;
190
  color: white;
191
  }
192
  .btn-rejoin:hover:not(:disabled) {
193
- background: #5a67d8;
 
194
  }
195
  .btn:disabled {
196
  opacity: 0.5;
197
  cursor: not-allowed;
198
  }
199
- .refresh-btn {
200
- background: white;
201
- color: #667eea;
202
- padding: 10px 20px;
203
- border: 2px solid #667eea;
204
- border-radius: 5px;
205
- cursor: pointer;
206
- font-weight: bold;
207
- margin-bottom: 20px;
208
- }
209
- .refresh-btn:hover {
210
- background: #667eea;
211
- color: white;
212
- }
213
  .timer {
214
- font-size: 0.9em;
215
  color: #999;
216
  margin-top: 5px;
217
  }
 
 
 
 
 
 
218
  </style>
219
  </head>
220
  <body>
221
  <div class="container">
222
- <h1>🤖 Mineflayer Bot Manager</h1>
 
 
 
223
 
224
  <div class="stats">
225
  <div class="stat-card">
@@ -234,12 +319,19 @@ HTML_TEMPLATE = """
234
  <h3>Disconnected</h3>
235
  <div class="number" id="disconnected-bots">0</div>
236
  </div>
 
 
 
 
237
  </div>
238
 
239
- <button class="refresh-btn" onclick="refreshBots()">🔄 Refresh from Sheet</button>
 
 
 
240
 
241
  <div class="bot-grid" id="bot-grid">
242
- <!-- Bot cards will be inserted here -->
243
  </div>
244
  </div>
245
 
@@ -257,6 +349,8 @@ HTML_TEMPLATE = """
257
  }
258
 
259
  async function rejoinBot(botName) {
 
 
260
  try {
261
  const response = await fetch('/api/rejoin', {
262
  method: 'POST',
@@ -270,18 +364,29 @@ HTML_TEMPLATE = """
270
  fetchBots();
271
  } catch (error) {
272
  console.error('Error rejoining bot:', error);
 
273
  }
274
  }
275
 
276
  async function refreshBots() {
 
 
 
 
277
  try {
278
- await fetch('/api/refresh', {method: 'POST'});
279
- fetchBots();
280
  } catch (error) {
281
- console.error('Error refreshing:', error);
282
  }
283
  }
284
 
 
 
 
 
 
 
285
  function updateUI() {
286
  const grid = document.getElementById('bot-grid');
287
  grid.innerHTML = '';
@@ -289,11 +394,13 @@ HTML_TEMPLATE = """
289
  let total = 0;
290
  let connected = 0;
291
  let disconnected = 0;
 
292
 
293
  for (const [botName, botInfo] of Object.entries(botsData)) {
294
  total++;
295
  if (botInfo.status === 'connected') connected++;
296
  else if (botInfo.status === 'disconnected') disconnected++;
 
297
 
298
  const card = document.createElement('div');
299
  card.className = 'bot-card';
@@ -303,36 +410,38 @@ HTML_TEMPLATE = """
303
  let timeRemaining = '';
304
 
305
  if (botInfo.time_until_rejoin && botInfo.time_until_rejoin > 0) {
306
- const minutes = Math.floor(botInfo.time_until_rejoin / 60);
307
- const seconds = botInfo.time_until_rejoin % 60;
308
- timeRemaining = `Wait: ${minutes}m ${seconds}s`;
309
  }
310
 
311
  card.innerHTML = `
312
- <div class="bot-name">${botName}</div>
313
- <div class="bot-info">Server: Hidden</div>
314
- <div class="bot-info">Version: ${botInfo.version}</div>
315
- <div class="status ${statusClass}">${botInfo.status.toUpperCase()}</div>
316
  ${timeRemaining ? `<div class="timer">${timeRemaining}</div>` : ''}
317
  ${botInfo.status === 'disconnected' ?
318
  `<button class="btn btn-rejoin"
319
  onclick="rejoinBot('${botName}')"
320
  ${!canRejoin ? 'disabled' : ''}>
321
- ${canRejoin ? 'Rejoin' : timeRemaining || 'Wait...'}
322
  </button>` : ''}
323
  `;
324
 
325
  grid.appendChild(card);
326
  }
327
 
 
 
 
 
328
  document.getElementById('total-bots').textContent = total;
329
  document.getElementById('connected-bots').textContent = connected;
330
  document.getElementById('disconnected-bots').textContent = disconnected;
 
331
  }
332
 
333
  // Initial fetch and periodic updates
334
  fetchBots();
335
- setInterval(fetchBots, 5000);
336
  </script>
337
  </body>
338
  </html>
@@ -344,24 +453,26 @@ def write_bot_script():
344
 
345
  def fetch_sheet_data():
346
  try:
347
- response = requests.get(SHEET_URL)
348
  if response.status_code == 200:
349
  lines = response.text.strip().split('\n')
350
  if len(lines) > 1:
351
  data = []
352
  for line in lines[1:]: # Skip header
353
  parts = line.split(',')
354
- if len(parts) >= 4:
355
  bot_name = parts[0].strip()
356
  ip = parts[1].strip()
357
  port = parts[2].strip()
358
  version = parts[3].strip() if len(parts) > 3 else "1.20.1"
359
- if bot_name and ip and port:
 
 
360
  data.append({
361
  'bot_name': bot_name,
362
  'ip': ip,
363
  'port': port,
364
- 'version': version
365
  })
366
  return data
367
  except Exception as e:
@@ -379,7 +490,9 @@ def start_bot(bot_data):
379
  # Kill existing process if any
380
  if bot_name in bot_processes:
381
  try:
382
- bot_processes[bot_name].terminate()
 
 
383
  except:
384
  pass
385
 
@@ -388,32 +501,27 @@ def start_bot(bot_data):
388
  process = subprocess.Popen(
389
  ['node', 'bot.js', bot_name, bot_data['ip'], bot_data['port'], bot_data['version']],
390
  stdout=subprocess.PIPE,
391
- stderr=subprocess.PIPE,
392
- text=True
 
393
  )
394
 
395
  bot_processes[bot_name] = process
396
  server_bots[server_key] = bot_name
397
 
398
- # Check initial connection
399
- time.sleep(2)
400
- output = ""
401
- if process.poll() is None:
402
- bots[bot_name] = {
403
- 'status': 'connecting',
404
- 'ip': bot_data['ip'],
405
- 'port': bot_data['port'],
406
- 'version': bot_data['version'],
407
- 'server_key': server_key
408
- }
409
-
410
- # Start monitoring thread
411
- threading.Thread(target=monitor_bot, args=(bot_name,), daemon=True).start()
412
- return True, "Bot started"
413
  except Exception as e:
414
  return False, str(e)
415
-
416
- return False, "Failed to start bot"
417
 
418
  def monitor_bot(bot_name):
419
  if bot_name not in bot_processes:
@@ -426,34 +534,55 @@ def monitor_bot(bot_name):
426
  # Process ended
427
  if bot_name in bots:
428
  bots[bot_name]['status'] = 'disconnected'
429
- # Remove from server_bots
430
  server_key = bots[bot_name].get('server_key')
431
- if server_key and server_key in server_bots:
432
  del server_bots[server_key]
433
  break
434
 
435
  try:
436
  line = process.stdout.readline()
437
  if line:
 
438
  if 'CONNECTED' in line:
439
- bots[bot_name]['status'] = 'connected'
440
- elif 'DISCONNECTED' in line or 'KICKED' in line or 'ERROR' in line:
441
- bots[bot_name]['status'] = 'disconnected'
442
- elif 'DIED' in line:
443
- bots[bot_name]['status'] = 'disconnected'
444
  except:
445
  pass
446
 
447
  time.sleep(0.1)
448
 
449
  def update_bots_from_sheet():
 
450
  sheet_data = fetch_sheet_data()
451
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  for bot_data in sheet_data:
453
  bot_name = bot_data['bot_name']
454
 
455
  if bot_name not in bots:
456
- # New bot, add it
457
  success, message = start_bot(bot_data)
458
  print(f"Starting {bot_name}: {message}")
459
 
@@ -495,18 +624,16 @@ def rejoin_bot():
495
 
496
  current_time = datetime.now()
497
 
498
- # Check if can rejoin (1 hour cooldown)
499
  if bot_name in last_rejoin_times:
500
  time_passed = current_time - last_rejoin_times[bot_name]
501
  if time_passed < timedelta(hours=1):
502
  remaining = timedelta(hours=1) - time_passed
503
  return jsonify({'error': f'Please wait {int(remaining.total_seconds() / 60)} minutes'}), 429
504
 
505
- # Check if bot is already connected
506
  if bots[bot_name]['status'] == 'connected':
507
  return jsonify({'error': 'Bot is already connected'}), 400
508
 
509
- # Rejoin the bot
510
  bot_data = {
511
  'bot_name': bot_name,
512
  'ip': bots[bot_name]['ip'],
@@ -527,7 +654,19 @@ def refresh():
527
  update_bots_from_sheet()
528
  return jsonify({'success': True})
529
 
 
 
 
 
 
 
 
 
 
530
  if __name__ == '__main__':
 
 
 
531
  # Write bot script
532
  write_bot_script()
533
 
@@ -539,9 +678,13 @@ if __name__ == '__main__':
539
  def periodic_update():
540
  while True:
541
  time.sleep(60) # Check every minute
542
- update_bots_from_sheet()
 
 
 
543
 
544
  threading.Thread(target=periodic_update, daemon=True).start()
545
 
546
  # Start Flask app
 
547
  app.run(host='0.0.0.0', port=7860, debug=False)
 
3
  import time
4
  import threading
5
  import requests
 
6
  from flask import Flask, render_template_string, jsonify, request
7
  from datetime import datetime, timedelta
8
  import subprocess
9
+ import signal
10
+ import sys
11
+
12
+
13
 
14
  app = Flask(__name__)
15
 
16
+ # Google Sheets configuration
17
  SHEET_ID = os.environ.get("SHEET_ID")
18
  SHEET_URL = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/export?format=csv"
 
19
  # Bot storage
20
  bots = {}
21
  bot_processes = {}
 
40
  username: botName,
41
  version: version,
42
  hideErrors: true,
43
+ checkTimeoutInterval: 60000,
44
+ auth: 'offline'
45
  });
46
 
47
+ let afkInterval;
48
+
49
  bot.on('spawn', () => {
50
  console.log('CONNECTED');
51
+
52
+ // Anti-AFK system
53
+ afkInterval = setInterval(() => {
54
  const actions = [
55
+ () => {
56
+ bot.setControlState('forward', true);
57
+ setTimeout(() => bot.setControlState('forward', false), 100);
58
+ },
59
+ () => {
60
+ bot.setControlState('back', true);
61
+ setTimeout(() => bot.setControlState('back', false), 100);
62
+ },
63
+ () => {
64
+ bot.setControlState('left', true);
65
+ setTimeout(() => bot.setControlState('left', false), 100);
66
+ },
67
+ () => {
68
+ bot.setControlState('right', true);
69
+ setTimeout(() => bot.setControlState('right', false), 100);
70
+ },
71
+ () => {
72
+ bot.setControlState('jump', true);
73
+ setTimeout(() => bot.setControlState('jump', false), 100);
74
+ },
75
+ () => {
76
+ bot.setControlState('sneak', true);
77
+ setTimeout(() => bot.setControlState('sneak', false), 100);
78
+ }
79
  ];
80
 
81
  const randomAction = actions[Math.floor(Math.random() * actions.length)];
82
  randomAction();
 
 
 
 
83
  }, 30000); // Move every 30 seconds
84
  });
85
 
86
  bot.on('death', () => {
87
  console.log('DIED');
88
+ setTimeout(() => {
89
+ if (bot.health !== undefined) {
90
+ bot.respawn();
91
+ }
92
+ }, 5000);
93
  });
94
 
95
  bot.on('kicked', (reason) => {
96
  console.log('KICKED:', reason);
97
+ if (afkInterval) clearInterval(afkInterval);
98
  });
99
 
100
  bot.on('error', (err) => {
 
103
 
104
  bot.on('end', () => {
105
  console.log('DISCONNECTED');
106
+ if (afkInterval) clearInterval(afkInterval);
107
+ process.exit();
108
+ });
109
+
110
+ // Handle process termination
111
+ process.on('SIGTERM', () => {
112
+ if (afkInterval) clearInterval(afkInterval);
113
+ bot.quit();
114
  process.exit();
115
  });
116
  """
 
120
  <html>
121
  <head>
122
  <title>Mineflayer Bot Manager</title>
123
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
124
  <style>
125
  * {
126
  margin: 0;
 
137
  max-width: 1400px;
138
  margin: 0 auto;
139
  }
140
+ .header {
 
141
  text-align: center;
142
  margin-bottom: 30px;
143
+ }
144
+ h1 {
145
+ color: white;
146
  text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
147
+ margin-bottom: 10px;
148
+ }
149
+ .subtitle {
150
+ color: rgba(255,255,255,0.9);
151
+ font-size: 0.9em;
152
  }
153
  .stats {
154
  display: flex;
 
163
  flex: 1;
164
  min-width: 200px;
165
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
166
+ animation: slideIn 0.5s ease;
167
  }
168
  .stat-card h3 {
169
  color: #667eea;
170
  margin-bottom: 10px;
171
+ font-size: 0.9em;
172
+ text-transform: uppercase;
173
+ letter-spacing: 1px;
174
  }
175
  .stat-card .number {
176
  font-size: 2em;
177
  font-weight: bold;
178
  color: #333;
179
  }
180
+ .controls {
181
+ margin-bottom: 20px;
182
+ display: flex;
183
+ gap: 10px;
184
+ flex-wrap: wrap;
185
+ }
186
+ .refresh-btn, .reload-sheet-btn {
187
+ background: white;
188
+ color: #667eea;
189
+ padding: 10px 20px;
190
+ border: 2px solid #667eea;
191
+ border-radius: 5px;
192
+ cursor: pointer;
193
+ font-weight: bold;
194
+ transition: all 0.3s;
195
+ }
196
+ .refresh-btn:hover, .reload-sheet-btn:hover {
197
+ background: #667eea;
198
+ color: white;
199
+ transform: translateY(-2px);
200
+ }
201
  .bot-grid {
202
  display: grid;
203
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
204
  gap: 20px;
205
  }
206
  .bot-card {
 
208
  border-radius: 10px;
209
  padding: 20px;
210
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
211
+ transition: all 0.3s;
212
+ animation: fadeIn 0.5s ease;
213
  }
214
  .bot-card:hover {
215
  transform: translateY(-5px);
216
+ box-shadow: 0 8px 15px rgba(0,0,0,0.2);
217
  }
218
  .bot-name {
219
+ font-size: 1.1em;
220
  font-weight: bold;
221
  color: #333;
222
  margin-bottom: 10px;
223
+ white-space: nowrap;
224
+ overflow: hidden;
225
+ text-overflow: ellipsis;
226
  }
227
  .bot-info {
228
  color: #666;
229
  margin: 5px 0;
230
+ font-size: 0.9em;
231
  }
232
  .status {
233
  display: inline-block;
234
+ padding: 5px 12px;
235
  border-radius: 20px;
236
+ font-size: 0.85em;
237
  font-weight: bold;
238
  margin: 10px 0;
239
+ text-transform: uppercase;
240
+ letter-spacing: 0.5px;
241
  }
242
  .status.connected {
243
+ background: linear-gradient(135deg, #10b981, #059669);
244
  color: white;
245
  }
246
  .status.disconnected {
247
+ background: linear-gradient(135deg, #ef4444, #dc2626);
248
  color: white;
249
  }
250
  .status.connecting {
251
+ background: linear-gradient(135deg, #f59e0b, #d97706);
252
  color: white;
253
+ animation: pulse 1.5s infinite;
254
+ }
255
+ @keyframes pulse {
256
+ 0%, 100% { opacity: 1; }
257
+ 50% { opacity: 0.7; }
258
+ }
259
+ @keyframes slideIn {
260
+ from { transform: translateX(-20px); opacity: 0; }
261
+ to { transform: translateX(0); opacity: 1; }
262
+ }
263
+ @keyframes fadeIn {
264
+ from { opacity: 0; }
265
+ to { opacity: 1; }
266
  }
267
  .btn {
268
+ padding: 8px 16px;
269
  border: none;
270
  border-radius: 5px;
271
  cursor: pointer;
272
  font-weight: bold;
273
  transition: all 0.3s;
274
  margin-top: 10px;
275
+ font-size: 0.9em;
276
  }
277
  .btn-rejoin {
278
+ background: linear-gradient(135deg, #667eea, #764ba2);
279
  color: white;
280
  }
281
  .btn-rejoin:hover:not(:disabled) {
282
+ transform: scale(1.05);
283
+ box-shadow: 0 4px 10px rgba(102, 126, 234, 0.4);
284
  }
285
  .btn:disabled {
286
  opacity: 0.5;
287
  cursor: not-allowed;
288
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  .timer {
290
+ font-size: 0.85em;
291
  color: #999;
292
  margin-top: 5px;
293
  }
294
+ .loading {
295
+ text-align: center;
296
+ color: white;
297
+ font-size: 1.2em;
298
+ margin-top: 50px;
299
+ }
300
  </style>
301
  </head>
302
  <body>
303
  <div class="container">
304
+ <div class="header">
305
+ <h1>🤖 Mineflayer Bot Manager</h1>
306
+ <div class="subtitle">Manage your Minecraft bots from Google Sheets</div>
307
+ </div>
308
 
309
  <div class="stats">
310
  <div class="stat-card">
 
319
  <h3>Disconnected</h3>
320
  <div class="number" id="disconnected-bots">0</div>
321
  </div>
322
+ <div class="stat-card">
323
+ <h3>Connecting</h3>
324
+ <div class="number" id="connecting-bots">0</div>
325
+ </div>
326
  </div>
327
 
328
+ <div class="controls">
329
+ <button class="refresh-btn" onclick="refreshBots()">🔄 Refresh Status</button>
330
+ <button class="reload-sheet-btn" onclick="reloadSheet()">📊 Reload from Sheet</button>
331
+ </div>
332
 
333
  <div class="bot-grid" id="bot-grid">
334
+ <div class="loading">Loading bots...</div>
335
  </div>
336
  </div>
337
 
 
349
  }
350
 
351
  async function rejoinBot(botName) {
352
+ if (!confirm(`Rejoin bot ${botName}?`)) return;
353
+
354
  try {
355
  const response = await fetch('/api/rejoin', {
356
  method: 'POST',
 
364
  fetchBots();
365
  } catch (error) {
366
  console.error('Error rejoining bot:', error);
367
+ alert('Failed to rejoin bot');
368
  }
369
  }
370
 
371
  async function refreshBots() {
372
+ await fetchBots();
373
+ }
374
+
375
+ async function reloadSheet() {
376
  try {
377
+ const response = await fetch('/api/refresh', {method: 'POST'});
378
+ await fetchBots();
379
  } catch (error) {
380
+ console.error('Error reloading sheet:', error);
381
  }
382
  }
383
 
384
+ function formatTime(seconds) {
385
+ const minutes = Math.floor(seconds / 60);
386
+ const secs = seconds % 60;
387
+ return `${minutes}m ${secs}s`;
388
+ }
389
+
390
  function updateUI() {
391
  const grid = document.getElementById('bot-grid');
392
  grid.innerHTML = '';
 
394
  let total = 0;
395
  let connected = 0;
396
  let disconnected = 0;
397
+ let connecting = 0;
398
 
399
  for (const [botName, botInfo] of Object.entries(botsData)) {
400
  total++;
401
  if (botInfo.status === 'connected') connected++;
402
  else if (botInfo.status === 'disconnected') disconnected++;
403
+ else if (botInfo.status === 'connecting') connecting++;
404
 
405
  const card = document.createElement('div');
406
  card.className = 'bot-card';
 
410
  let timeRemaining = '';
411
 
412
  if (botInfo.time_until_rejoin && botInfo.time_until_rejoin > 0) {
413
+ timeRemaining = `⏱️ ${formatTime(botInfo.time_until_rejoin)}`;
 
 
414
  }
415
 
416
  card.innerHTML = `
417
+ <div class="bot-name" title="${botName}">🎮 ${botName}</div>
418
+ <div class="bot-info">📦 Version: ${botInfo.version}</div>
419
+ <div class="status ${statusClass}">${botInfo.status}</div>
 
420
  ${timeRemaining ? `<div class="timer">${timeRemaining}</div>` : ''}
421
  ${botInfo.status === 'disconnected' ?
422
  `<button class="btn btn-rejoin"
423
  onclick="rejoinBot('${botName}')"
424
  ${!canRejoin ? 'disabled' : ''}>
425
+ ${canRejoin ? '🔄 Rejoin' : '⏳ ' + (timeRemaining || 'Cooldown')}
426
  </button>` : ''}
427
  `;
428
 
429
  grid.appendChild(card);
430
  }
431
 
432
+ if (total === 0) {
433
+ grid.innerHTML = '<div class="loading">No bots configured. Add bots to your Google Sheet.</div>';
434
+ }
435
+
436
  document.getElementById('total-bots').textContent = total;
437
  document.getElementById('connected-bots').textContent = connected;
438
  document.getElementById('disconnected-bots').textContent = disconnected;
439
+ document.getElementById('connecting-bots').textContent = connecting;
440
  }
441
 
442
  // Initial fetch and periodic updates
443
  fetchBots();
444
+ setInterval(fetchBots, 3000); // Update every 3 seconds
445
  </script>
446
  </body>
447
  </html>
 
453
 
454
  def fetch_sheet_data():
455
  try:
456
+ response = requests.get(SHEET_URL, timeout=10)
457
  if response.status_code == 200:
458
  lines = response.text.strip().split('\n')
459
  if len(lines) > 1:
460
  data = []
461
  for line in lines[1:]: # Skip header
462
  parts = line.split(',')
463
+ if len(parts) >= 3:
464
  bot_name = parts[0].strip()
465
  ip = parts[1].strip()
466
  port = parts[2].strip()
467
  version = parts[3].strip() if len(parts) > 3 else "1.20.1"
468
+
469
+ # Skip empty rows
470
+ if bot_name and ip and port and ip != "" and port.isdigit():
471
  data.append({
472
  'bot_name': bot_name,
473
  'ip': ip,
474
  'port': port,
475
+ 'version': version if version else "1.20.1"
476
  })
477
  return data
478
  except Exception as e:
 
490
  # Kill existing process if any
491
  if bot_name in bot_processes:
492
  try:
493
+ process = bot_processes[bot_name]
494
+ process.terminate()
495
+ process.wait(timeout=5)
496
  except:
497
  pass
498
 
 
501
  process = subprocess.Popen(
502
  ['node', 'bot.js', bot_name, bot_data['ip'], bot_data['port'], bot_data['version']],
503
  stdout=subprocess.PIPE,
504
+ stderr=subprocess.STDOUT,
505
+ text=True,
506
+ bufsize=1
507
  )
508
 
509
  bot_processes[bot_name] = process
510
  server_bots[server_key] = bot_name
511
 
512
+ bots[bot_name] = {
513
+ 'status': 'connecting',
514
+ 'ip': bot_data['ip'],
515
+ 'port': bot_data['port'],
516
+ 'version': bot_data['version'],
517
+ 'server_key': server_key
518
+ }
519
+
520
+ # Start monitoring thread
521
+ threading.Thread(target=monitor_bot, args=(bot_name,), daemon=True).start()
522
+ return True, "Bot started"
 
 
 
 
523
  except Exception as e:
524
  return False, str(e)
 
 
525
 
526
  def monitor_bot(bot_name):
527
  if bot_name not in bot_processes:
 
534
  # Process ended
535
  if bot_name in bots:
536
  bots[bot_name]['status'] = 'disconnected'
 
537
  server_key = bots[bot_name].get('server_key')
538
+ if server_key and server_key in server_bots and server_bots[server_key] == bot_name:
539
  del server_bots[server_key]
540
  break
541
 
542
  try:
543
  line = process.stdout.readline()
544
  if line:
545
+ print(f"[{bot_name}] {line.strip()}")
546
  if 'CONNECTED' in line:
547
+ if bot_name in bots:
548
+ bots[bot_name]['status'] = 'connected'
549
+ elif any(x in line for x in ['DISCONNECTED', 'KICKED', 'ERROR', 'DIED']):
550
+ if bot_name in bots:
551
+ bots[bot_name]['status'] = 'disconnected'
552
  except:
553
  pass
554
 
555
  time.sleep(0.1)
556
 
557
  def update_bots_from_sheet():
558
+ print("Updating bots from sheet...")
559
  sheet_data = fetch_sheet_data()
560
 
561
+ # Remove bots that are no longer in sheet
562
+ current_bot_names = {bot['bot_name'] for bot in sheet_data}
563
+ bots_to_remove = []
564
+
565
+ for bot_name in list(bots.keys()):
566
+ if bot_name not in current_bot_names:
567
+ bots_to_remove.append(bot_name)
568
+
569
+ for bot_name in bots_to_remove:
570
+ if bot_name in bot_processes:
571
+ try:
572
+ bot_processes[bot_name].terminate()
573
+ except:
574
+ pass
575
+ if bot_name in bots:
576
+ server_key = bots[bot_name].get('server_key')
577
+ if server_key in server_bots:
578
+ del server_bots[server_key]
579
+ del bots[bot_name]
580
+
581
+ # Add new bots
582
  for bot_data in sheet_data:
583
  bot_name = bot_data['bot_name']
584
 
585
  if bot_name not in bots:
 
586
  success, message = start_bot(bot_data)
587
  print(f"Starting {bot_name}: {message}")
588
 
 
624
 
625
  current_time = datetime.now()
626
 
627
+ # Check cooldown
628
  if bot_name in last_rejoin_times:
629
  time_passed = current_time - last_rejoin_times[bot_name]
630
  if time_passed < timedelta(hours=1):
631
  remaining = timedelta(hours=1) - time_passed
632
  return jsonify({'error': f'Please wait {int(remaining.total_seconds() / 60)} minutes'}), 429
633
 
 
634
  if bots[bot_name]['status'] == 'connected':
635
  return jsonify({'error': 'Bot is already connected'}), 400
636
 
 
637
  bot_data = {
638
  'bot_name': bot_name,
639
  'ip': bots[bot_name]['ip'],
 
654
  update_bots_from_sheet()
655
  return jsonify({'success': True})
656
 
657
+ def signal_handler(sig, frame):
658
+ print("\nShutting down gracefully...")
659
+ for bot_name, process in bot_processes.items():
660
+ try:
661
+ process.terminate()
662
+ except:
663
+ pass
664
+ sys.exit(0)
665
+
666
  if __name__ == '__main__':
667
+ signal.signal(signal.SIGINT, signal_handler)
668
+ signal.signal(signal.SIGTERM, signal_handler)
669
+
670
  # Write bot script
671
  write_bot_script()
672
 
 
678
  def periodic_update():
679
  while True:
680
  time.sleep(60) # Check every minute
681
+ try:
682
+ update_bots_from_sheet()
683
+ except Exception as e:
684
+ print(f"Error in periodic update: {e}")
685
 
686
  threading.Thread(target=periodic_update, daemon=True).start()
687
 
688
  # Start Flask app
689
+ print("Starting web server on port 7860...")
690
  app.run(host='0.0.0.0', port=7860, debug=False)