Update app.py
Browse files
app.py
CHANGED
|
@@ -9,13 +9,17 @@ 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 = {}
|
|
@@ -297,6 +301,13 @@ HTML_TEMPLATE = """
|
|
| 297 |
font-size: 1.2em;
|
| 298 |
margin-top: 50px;
|
| 299 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
</style>
|
| 301 |
</head>
|
| 302 |
<body>
|
|
@@ -330,6 +341,8 @@ HTML_TEMPLATE = """
|
|
| 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>
|
|
@@ -341,10 +354,13 @@ HTML_TEMPLATE = """
|
|
| 341 |
async function fetchBots() {
|
| 342 |
try {
|
| 343 |
const response = await fetch('/api/bots');
|
|
|
|
| 344 |
botsData = await response.json();
|
| 345 |
updateUI();
|
|
|
|
| 346 |
} catch (error) {
|
| 347 |
console.error('Error fetching bots:', error);
|
|
|
|
| 348 |
}
|
| 349 |
}
|
| 350 |
|
|
@@ -360,8 +376,9 @@ HTML_TEMPLATE = """
|
|
| 360 |
const result = await response.json();
|
| 361 |
if (result.error) {
|
| 362 |
alert(result.error);
|
|
|
|
|
|
|
| 363 |
}
|
| 364 |
-
fetchBots();
|
| 365 |
} catch (error) {
|
| 366 |
console.error('Error rejoining bot:', error);
|
| 367 |
alert('Failed to rejoin bot');
|
|
@@ -375,12 +392,24 @@ HTML_TEMPLATE = """
|
|
| 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;
|
|
@@ -413,14 +442,17 @@ HTML_TEMPLATE = """
|
|
| 413 |
timeRemaining = `⏱️ ${formatTime(botInfo.time_until_rejoin)}`;
|
| 414 |
}
|
| 415 |
|
|
|
|
|
|
|
|
|
|
| 416 |
card.innerHTML = `
|
| 417 |
-
<div class="bot-name" title="${
|
| 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('${
|
| 424 |
${!canRejoin ? 'disabled' : ''}>
|
| 425 |
${canRejoin ? '🔄 Rejoin' : '⏳ ' + (timeRemaining || 'Cooldown')}
|
| 426 |
</button>` : ''}
|
|
@@ -448,10 +480,19 @@ HTML_TEMPLATE = """
|
|
| 448 |
"""
|
| 449 |
|
| 450 |
def write_bot_script():
|
| 451 |
-
|
| 452 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 453 |
|
| 454 |
def fetch_sheet_data():
|
|
|
|
| 455 |
try:
|
| 456 |
response = requests.get(SHEET_URL, timeout=10)
|
| 457 |
if response.status_code == 200:
|
|
@@ -459,27 +500,30 @@ def fetch_sheet_data():
|
|
| 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
|
| 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:
|
| 479 |
print(f"Error fetching sheet: {e}")
|
| 480 |
return []
|
| 481 |
|
| 482 |
def start_bot(bot_data):
|
|
|
|
| 483 |
bot_name = bot_data['bot_name']
|
| 484 |
server_key = f"{bot_data['ip']}:{bot_data['port']}"
|
| 485 |
|
|
@@ -498,12 +542,14 @@ def start_bot(bot_data):
|
|
| 498 |
|
| 499 |
# Start new bot process
|
| 500 |
try:
|
|
|
|
| 501 |
process = subprocess.Popen(
|
| 502 |
-
['node',
|
| 503 |
stdout=subprocess.PIPE,
|
| 504 |
stderr=subprocess.STDOUT,
|
| 505 |
text=True,
|
| 506 |
-
bufsize=1
|
|
|
|
| 507 |
)
|
| 508 |
|
| 509 |
bot_processes[bot_name] = process
|
|
@@ -521,9 +567,11 @@ def start_bot(bot_data):
|
|
| 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:
|
| 528 |
return
|
| 529 |
|
|
@@ -555,6 +603,7 @@ def monitor_bot(bot_name):
|
|
| 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 |
|
|
@@ -577,6 +626,7 @@ def update_bots_from_sheet():
|
|
| 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:
|
|
@@ -592,6 +642,7 @@ def index():
|
|
| 592 |
|
| 593 |
@app.route('/api/bots')
|
| 594 |
def get_bots():
|
|
|
|
| 595 |
result = {}
|
| 596 |
current_time = datetime.now()
|
| 597 |
|
|
@@ -616,6 +667,7 @@ def get_bots():
|
|
| 616 |
|
| 617 |
@app.route('/api/rejoin', methods=['POST'])
|
| 618 |
def rejoin_bot():
|
|
|
|
| 619 |
data = request.json
|
| 620 |
bot_name = data.get('bot_name')
|
| 621 |
|
|
@@ -651,10 +703,12 @@ def rejoin_bot():
|
|
| 651 |
|
| 652 |
@app.route('/api/refresh', methods=['POST'])
|
| 653 |
def refresh():
|
|
|
|
| 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:
|
|
@@ -667,8 +721,10 @@ 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 |
|
| 673 |
# Initial load from sheet
|
| 674 |
print("Loading bots from Google Sheet...")
|
|
|
|
| 9 |
import signal
|
| 10 |
import sys
|
| 11 |
|
|
|
|
|
|
|
| 12 |
app = Flask(__name__)
|
| 13 |
|
| 14 |
+
# Working directory for bot files
|
| 15 |
+
WORK_DIR = "/tmp"
|
| 16 |
+
BOT_SCRIPT_PATH = os.path.join(WORK_DIR, "bot.js")
|
| 17 |
+
|
| 18 |
# Google Sheets configuration
|
| 19 |
+
|
| 20 |
SHEET_ID = os.environ.get("SHEET_ID")
|
| 21 |
SHEET_URL = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/export?format=csv"
|
| 22 |
+
|
| 23 |
# Bot storage
|
| 24 |
bots = {}
|
| 25 |
bot_processes = {}
|
|
|
|
| 301 |
font-size: 1.2em;
|
| 302 |
margin-top: 50px;
|
| 303 |
}
|
| 304 |
+
.error-msg {
|
| 305 |
+
background: #fee;
|
| 306 |
+
color: #c00;
|
| 307 |
+
padding: 10px;
|
| 308 |
+
border-radius: 5px;
|
| 309 |
+
margin: 10px 0;
|
| 310 |
+
}
|
| 311 |
</style>
|
| 312 |
</head>
|
| 313 |
<body>
|
|
|
|
| 341 |
<button class="reload-sheet-btn" onclick="reloadSheet()">📊 Reload from Sheet</button>
|
| 342 |
</div>
|
| 343 |
|
| 344 |
+
<div id="error-container"></div>
|
| 345 |
+
|
| 346 |
<div class="bot-grid" id="bot-grid">
|
| 347 |
<div class="loading">Loading bots...</div>
|
| 348 |
</div>
|
|
|
|
| 354 |
async function fetchBots() {
|
| 355 |
try {
|
| 356 |
const response = await fetch('/api/bots');
|
| 357 |
+
if (!response.ok) throw new Error('Failed to fetch');
|
| 358 |
botsData = await response.json();
|
| 359 |
updateUI();
|
| 360 |
+
clearError();
|
| 361 |
} catch (error) {
|
| 362 |
console.error('Error fetching bots:', error);
|
| 363 |
+
showError('Failed to fetch bot status');
|
| 364 |
}
|
| 365 |
}
|
| 366 |
|
|
|
|
| 376 |
const result = await response.json();
|
| 377 |
if (result.error) {
|
| 378 |
alert(result.error);
|
| 379 |
+
} else {
|
| 380 |
+
await fetchBots();
|
| 381 |
}
|
|
|
|
| 382 |
} catch (error) {
|
| 383 |
console.error('Error rejoining bot:', error);
|
| 384 |
alert('Failed to rejoin bot');
|
|
|
|
| 392 |
async function reloadSheet() {
|
| 393 |
try {
|
| 394 |
const response = await fetch('/api/refresh', {method: 'POST'});
|
| 395 |
+
if (!response.ok) throw new Error('Failed to reload');
|
| 396 |
await fetchBots();
|
| 397 |
+
clearError();
|
| 398 |
} catch (error) {
|
| 399 |
console.error('Error reloading sheet:', error);
|
| 400 |
+
showError('Failed to reload from sheet');
|
| 401 |
}
|
| 402 |
}
|
| 403 |
|
| 404 |
+
function showError(message) {
|
| 405 |
+
const container = document.getElementById('error-container');
|
| 406 |
+
container.innerHTML = `<div class="error-msg">${message}</div>`;
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
function clearError() {
|
| 410 |
+
document.getElementById('error-container').innerHTML = '';
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
function formatTime(seconds) {
|
| 414 |
const minutes = Math.floor(seconds / 60);
|
| 415 |
const secs = seconds % 60;
|
|
|
|
| 442 |
timeRemaining = `⏱️ ${formatTime(botInfo.time_until_rejoin)}`;
|
| 443 |
}
|
| 444 |
|
| 445 |
+
// Escape bot name for HTML
|
| 446 |
+
const safeBotName = botName.replace(/'/g, "\\'").replace(/"/g, """);
|
| 447 |
+
|
| 448 |
card.innerHTML = `
|
| 449 |
+
<div class="bot-name" title="${safeBotName}">🎮 ${safeBotName}</div>
|
| 450 |
<div class="bot-info">📦 Version: ${botInfo.version}</div>
|
| 451 |
<div class="status ${statusClass}">${botInfo.status}</div>
|
| 452 |
${timeRemaining ? `<div class="timer">${timeRemaining}</div>` : ''}
|
| 453 |
${botInfo.status === 'disconnected' ?
|
| 454 |
`<button class="btn btn-rejoin"
|
| 455 |
+
onclick="rejoinBot('${safeBotName}')"
|
| 456 |
${!canRejoin ? 'disabled' : ''}>
|
| 457 |
${canRejoin ? '🔄 Rejoin' : '⏳ ' + (timeRemaining || 'Cooldown')}
|
| 458 |
</button>` : ''}
|
|
|
|
| 480 |
"""
|
| 481 |
|
| 482 |
def write_bot_script():
|
| 483 |
+
"""Write the bot script to /tmp directory"""
|
| 484 |
+
try:
|
| 485 |
+
os.makedirs(WORK_DIR, exist_ok=True)
|
| 486 |
+
with open(BOT_SCRIPT_PATH, 'w') as f:
|
| 487 |
+
f.write(BOT_SCRIPT)
|
| 488 |
+
print(f"Bot script written to {BOT_SCRIPT_PATH}")
|
| 489 |
+
return True
|
| 490 |
+
except Exception as e:
|
| 491 |
+
print(f"Error writing bot script: {e}")
|
| 492 |
+
return False
|
| 493 |
|
| 494 |
def fetch_sheet_data():
|
| 495 |
+
"""Fetch bot configuration from Google Sheets"""
|
| 496 |
try:
|
| 497 |
response = requests.get(SHEET_URL, timeout=10)
|
| 498 |
if response.status_code == 200:
|
|
|
|
| 500 |
if len(lines) > 1:
|
| 501 |
data = []
|
| 502 |
for line in lines[1:]: # Skip header
|
| 503 |
+
# Handle CSV parsing properly
|
| 504 |
parts = line.split(',')
|
| 505 |
if len(parts) >= 3:
|
| 506 |
+
bot_name = parts[0].strip().strip('"')
|
| 507 |
+
ip = parts[1].strip().strip('"')
|
| 508 |
+
port = parts[2].strip().strip('"')
|
| 509 |
+
version = parts[3].strip().strip('"') if len(parts) > 3 else "1.20.1"
|
| 510 |
|
| 511 |
# Skip empty rows
|
| 512 |
+
if bot_name and ip and port and port.isdigit():
|
| 513 |
data.append({
|
| 514 |
'bot_name': bot_name,
|
| 515 |
'ip': ip,
|
| 516 |
'port': port,
|
| 517 |
'version': version if version else "1.20.1"
|
| 518 |
})
|
| 519 |
+
print(f"Fetched {len(data)} bots from sheet")
|
| 520 |
return data
|
| 521 |
except Exception as e:
|
| 522 |
print(f"Error fetching sheet: {e}")
|
| 523 |
return []
|
| 524 |
|
| 525 |
def start_bot(bot_data):
|
| 526 |
+
"""Start a Minecraft bot"""
|
| 527 |
bot_name = bot_data['bot_name']
|
| 528 |
server_key = f"{bot_data['ip']}:{bot_data['port']}"
|
| 529 |
|
|
|
|
| 542 |
|
| 543 |
# Start new bot process
|
| 544 |
try:
|
| 545 |
+
# Change working directory to /app where node_modules are installed
|
| 546 |
process = subprocess.Popen(
|
| 547 |
+
['node', BOT_SCRIPT_PATH, bot_name, bot_data['ip'], bot_data['port'], bot_data['version']],
|
| 548 |
stdout=subprocess.PIPE,
|
| 549 |
stderr=subprocess.STDOUT,
|
| 550 |
text=True,
|
| 551 |
+
bufsize=1,
|
| 552 |
+
cwd='/app' # Use /app as working directory
|
| 553 |
)
|
| 554 |
|
| 555 |
bot_processes[bot_name] = process
|
|
|
|
| 567 |
threading.Thread(target=monitor_bot, args=(bot_name,), daemon=True).start()
|
| 568 |
return True, "Bot started"
|
| 569 |
except Exception as e:
|
| 570 |
+
print(f"Error starting bot {bot_name}: {e}")
|
| 571 |
return False, str(e)
|
| 572 |
|
| 573 |
def monitor_bot(bot_name):
|
| 574 |
+
"""Monitor bot process output"""
|
| 575 |
if bot_name not in bot_processes:
|
| 576 |
return
|
| 577 |
|
|
|
|
| 603 |
time.sleep(0.1)
|
| 604 |
|
| 605 |
def update_bots_from_sheet():
|
| 606 |
+
"""Update bots from Google Sheet"""
|
| 607 |
print("Updating bots from sheet...")
|
| 608 |
sheet_data = fetch_sheet_data()
|
| 609 |
|
|
|
|
| 626 |
if server_key in server_bots:
|
| 627 |
del server_bots[server_key]
|
| 628 |
del bots[bot_name]
|
| 629 |
+
print(f"Removed bot: {bot_name}")
|
| 630 |
|
| 631 |
# Add new bots
|
| 632 |
for bot_data in sheet_data:
|
|
|
|
| 642 |
|
| 643 |
@app.route('/api/bots')
|
| 644 |
def get_bots():
|
| 645 |
+
"""Get all bot statuses"""
|
| 646 |
result = {}
|
| 647 |
current_time = datetime.now()
|
| 648 |
|
|
|
|
| 667 |
|
| 668 |
@app.route('/api/rejoin', methods=['POST'])
|
| 669 |
def rejoin_bot():
|
| 670 |
+
"""Rejoin a disconnected bot"""
|
| 671 |
data = request.json
|
| 672 |
bot_name = data.get('bot_name')
|
| 673 |
|
|
|
|
| 703 |
|
| 704 |
@app.route('/api/refresh', methods=['POST'])
|
| 705 |
def refresh():
|
| 706 |
+
"""Refresh bots from Google Sheet"""
|
| 707 |
update_bots_from_sheet()
|
| 708 |
return jsonify({'success': True})
|
| 709 |
|
| 710 |
def signal_handler(sig, frame):
|
| 711 |
+
"""Handle shutdown gracefully"""
|
| 712 |
print("\nShutting down gracefully...")
|
| 713 |
for bot_name, process in bot_processes.items():
|
| 714 |
try:
|
|
|
|
| 721 |
signal.signal(signal.SIGINT, signal_handler)
|
| 722 |
signal.signal(signal.SIGTERM, signal_handler)
|
| 723 |
|
| 724 |
+
# Write bot script to /tmp
|
| 725 |
+
if not write_bot_script():
|
| 726 |
+
print("Failed to write bot script, exiting...")
|
| 727 |
+
sys.exit(1)
|
| 728 |
|
| 729 |
# Initial load from sheet
|
| 730 |
print("Loading bots from Google Sheet...")
|