Tokipo commited on
Commit
effc3d9
·
verified ·
1 Parent(s): 14e43e4

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +547 -0
app.py ADDED
@@ -0,0 +1,547 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ 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 = {}
20
+ last_rejoin_times = {}
21
+ server_bots = {} # Track one bot per server
22
+
23
+ # Node.js bot script
24
+ BOT_SCRIPT = """
25
+ const mineflayer = require('mineflayer');
26
+ const args = process.argv.slice(2);
27
+
28
+ const botName = args[0];
29
+ const host = args[1];
30
+ const port = parseInt(args[2]);
31
+ const version = args[3] || '1.20.1';
32
+
33
+ console.log(`Starting bot ${botName} for ${host}:${port}`);
34
+
35
+ const bot = mineflayer.createBot({
36
+ host: host,
37
+ port: port,
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) => {
74
+ console.log('ERROR:', err.message);
75
+ });
76
+
77
+ bot.on('end', () => {
78
+ console.log('DISCONNECTED');
79
+ process.exit();
80
+ });
81
+ """
82
+
83
+ HTML_TEMPLATE = """
84
+ <!DOCTYPE html>
85
+ <html>
86
+ <head>
87
+ <title>Mineflayer Bot Manager</title>
88
+ <style>
89
+ * {
90
+ margin: 0;
91
+ padding: 0;
92
+ box-sizing: border-box;
93
+ }
94
+ body {
95
+ font-family: 'Segoe UI', Arial, sans-serif;
96
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
97
+ min-height: 100vh;
98
+ padding: 20px;
99
+ }
100
+ .container {
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;
112
+ gap: 20px;
113
+ margin-bottom: 30px;
114
+ flex-wrap: wrap;
115
+ }
116
+ .stat-card {
117
+ background: white;
118
+ padding: 20px;
119
+ border-radius: 10px;
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 {
139
+ background: white;
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">
226
+ <h3>Total Bots</h3>
227
+ <div class="number" id="total-bots">0</div>
228
+ </div>
229
+ <div class="stat-card">
230
+ <h3>Connected</h3>
231
+ <div class="number" id="connected-bots">0</div>
232
+ </div>
233
+ <div class="stat-card">
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
+
246
+ <script>
247
+ let botsData = {};
248
+
249
+ async function fetchBots() {
250
+ try {
251
+ const response = await fetch('/api/bots');
252
+ botsData = await response.json();
253
+ updateUI();
254
+ } catch (error) {
255
+ console.error('Error fetching bots:', error);
256
+ }
257
+ }
258
+
259
+ async function rejoinBot(botName) {
260
+ try {
261
+ const response = await fetch('/api/rejoin', {
262
+ method: 'POST',
263
+ headers: {'Content-Type': 'application/json'},
264
+ body: JSON.stringify({bot_name: botName})
265
+ });
266
+ const result = await response.json();
267
+ if (result.error) {
268
+ alert(result.error);
269
+ }
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 = '';
288
+
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';
300
+
301
+ let statusClass = botInfo.status;
302
+ let canRejoin = botInfo.can_rejoin;
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>
339
+ """
340
+
341
+ def write_bot_script():
342
+ with open('bot.js', 'w') as f:
343
+ f.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:
368
+ print(f"Error fetching sheet: {e}")
369
+ return []
370
+
371
+ def start_bot(bot_data):
372
+ bot_name = bot_data['bot_name']
373
+ server_key = f"{bot_data['ip']}:{bot_data['port']}"
374
+
375
+ # Check if server already has a bot
376
+ if server_key in server_bots and server_bots[server_key] != bot_name:
377
+ return False, "Server already has a bot"
378
+
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
+
386
+ # Start new bot process
387
+ try:
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:
420
+ return
421
+
422
+ process = bot_processes[bot_name]
423
+
424
+ while True:
425
+ if process.poll() is not None:
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
+
460
+ @app.route('/')
461
+ def index():
462
+ return render_template_string(HTML_TEMPLATE)
463
+
464
+ @app.route('/api/bots')
465
+ def get_bots():
466
+ result = {}
467
+ current_time = datetime.now()
468
+
469
+ for bot_name, bot_info in bots.items():
470
+ can_rejoin = True
471
+ time_until_rejoin = 0
472
+
473
+ if bot_name in last_rejoin_times:
474
+ time_passed = current_time - last_rejoin_times[bot_name]
475
+ if time_passed < timedelta(hours=1):
476
+ can_rejoin = False
477
+ time_until_rejoin = int((timedelta(hours=1) - time_passed).total_seconds())
478
+
479
+ result[bot_name] = {
480
+ 'status': bot_info['status'],
481
+ 'version': bot_info['version'],
482
+ 'can_rejoin': can_rejoin,
483
+ 'time_until_rejoin': time_until_rejoin
484
+ }
485
+
486
+ return jsonify(result)
487
+
488
+ @app.route('/api/rejoin', methods=['POST'])
489
+ def rejoin_bot():
490
+ data = request.json
491
+ bot_name = data.get('bot_name')
492
+
493
+ if bot_name not in bots:
494
+ return jsonify({'error': 'Bot not found'}), 404
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'],
513
+ 'port': bots[bot_name]['port'],
514
+ 'version': bots[bot_name]['version']
515
+ }
516
+
517
+ success, message = start_bot(bot_data)
518
+
519
+ if success:
520
+ last_rejoin_times[bot_name] = current_time
521
+ return jsonify({'success': True, 'message': message})
522
+ else:
523
+ return jsonify({'error': message}), 500
524
+
525
+ @app.route('/api/refresh', methods=['POST'])
526
+ 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
+
534
+ # Initial load from sheet
535
+ print("Loading bots from Google Sheet...")
536
+ update_bots_from_sheet()
537
+
538
+ # Start periodic sheet updates
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)