KiyEngine / app.py
Kiy-K's picture
Create app.py
9af2e8d verified
import os
import subprocess
import threading
import time
import sys
import queue
from flask import Flask, Response, render_template_string
# ==============================================================================
# 📟 KIYENGINE COMMAND CENTER (TERMINAL UI)
# ==============================================================================
app = Flask(__name__)
# Hàng đợi để chứa log từ bot (giữ lại 100 dòng cuối cùng)
log_queue = queue.Queue()
MAX_LOG_LINES = 200
recent_logs = []
def enqueue_output(out, queue):
"""Đọc output từ process và đẩy vào hàng đợi"""
for line in iter(out.readline, b''):
line = line.decode('utf-8').rstrip()
if line:
print(f"[BOT] {line}") # In ra console thật của Docker
queue.put(line)
# Lưu vào bộ nhớ đệm để người mới vào xem được lịch sử
recent_logs.append(line)
if len(recent_logs) > MAX_LOG_LINES:
recent_logs.pop(0)
out.close()
def prepare_config():
"""Inject Token vào config.yml"""
token = os.getenv("LICHESS_TOKEN")
config_path = "config.yml"
if not os.path.exists(config_path):
log_queue.put("❌ ERROR: config.yml not found!")
return False
if token:
try:
with open(config_path, "r", encoding="utf-8") as f:
content = f.read()
if "REPLACE_ME_WITH_LICHESS_TOKEN" in content:
log_queue.put("🔑 System: Injecting Lichess Token...")
new_content = content.replace("REPLACE_ME_WITH_LICHESS_TOKEN", token)
with open(config_path, "w", encoding="utf-8") as f:
f.write(new_content)
except Exception as e:
log_queue.put(f"❌ Config Error: {e}")
return False
else:
log_queue.put("⚠️ Warning: No LICHESS_TOKEN found in env.")
return True
def run_bot_process():
"""Chạy Lichess-bot trong subprocess"""
if not prepare_config():
return
log_queue.put("🚀 System: Starting KiyEngine v6 Assassin...")
log_queue.put("Target: Lichess.org | Mode: Hunter")
log_queue.put("--------------------------------------------------")
# Chạy bot với chế độ unbuffered (-u) để log ra ngay lập tức
process = subprocess.Popen(
["python3", "-u", "lichess-bot.py"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, # Gộp lỗi vào log chính
bufsize=1 # Line buffered
)
# Tạo thread đọc log
t = threading.Thread(target=enqueue_output, args=(process.stdout, log_queue))
t.daemon = True
t.start()
process.wait()
log_queue.put("🛑 System: Bot process terminated.")
# --- GIAO DIỆN WEB TERMINAL ---
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KiyEngine v6 Control Center</title>
<style>
body {
background-color: #0d1117;
color: #c9d1d9;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
height: 100vh;
box-sizing: border_box;
}
.header {
border-bottom: 1px solid #30363d;
padding-bottom: 10px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
font-size: 1.2rem;
font-weight: bold;
color: #58a6ff;
}
.status {
font-size: 0.9rem;
color: #2ea043;
}
.status::before {
content: '● ';
color: #2ea043;
animation: blink 1.5s infinite;
}
@keyframes blink { 50% { opacity: 0; } }
#terminal {
flex-grow: 1;
background-color: #010409;
border: 1px solid #30363d;
border-radius: 6px;
padding: 15px;
overflow-y: auto;
white-space: pre-wrap;
font-size: 0.9rem;
line-height: 1.4;
box-shadow: 0 0 15px rgba(0,0,0,0.5);
}
.log-line { margin: 2px 0; }
.log-info { color: #8b949e; }
.log-warn { color: #d29922; }
.log-err { color: #f85149; }
.log-success { color: #2ea043; }
.prompt { color: #79c0ff; margin-right: 5px; }
/* Scrollbar đẹp */
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-track { background: #0d1117; }
::-webkit-scrollbar-thumb { background: #30363d; border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { background: #58a6ff; }
</style>
</head>
<body>
<div class="header">
<div class="title">KiyEngine v6 Assassin <span style="font-size:0.8em; color:#8b949e;">[BitNet 1.58-bit]</span></div>
<div class="status">SYSTEM ONLINE</div>
</div>
<div id="terminal"></div>
<script>
const terminal = document.getElementById('terminal');
function appendLog(text) {
const div = document.createElement('div');
div.className = 'log-line';
// Tô màu log cơ bản
if (text.includes('ERROR') || text.includes('Traceback')) div.classList.add('log-err');
else if (text.includes('WARNING')) div.classList.add('log-warn');
else if (text.includes('Welcome') || text.includes('Connected')) div.classList.add('log-success');
else if (text.includes('INFO')) div.classList.add('log-info');
// Thêm dấu nhắc lệnh giả
div.innerHTML = '<span class="prompt">$</span>' + text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
terminal.appendChild(div);
// Auto scroll
terminal.scrollTop = terminal.scrollHeight;
}
// Kết nối luồng sự kiện (Server-Sent Events)
const evtSource = new EventSource("/stream_logs");
evtSource.onmessage = function(e) {
appendLog(e.data);
};
evtSource.onerror = function() {
console.log("Connection lost, retrying...");
};
</script>
</body>
</html>
"""
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/stream_logs')
def stream_logs():
def generate():
# Gửi lại các log cũ trước
for line in recent_logs:
yield f"data: {line}\n\n"
# Gửi log mới realtime
while True:
try:
# Chờ log mới trong 5s, nếu không có thì gửi heartbeat comment
line = log_queue.get(timeout=5)
yield f"data: {line}\n\n"
except queue.Empty:
yield ": keep-alive\n\n" # Comment để giữ kết nối không bị đóng
return Response(generate(), mimetype='text/event-stream')
if __name__ == "__main__":
# 1. Chạy Bot ở thread riêng
threading.Thread(target=run_bot_process, daemon=True).start()
# 2. Chạy Web Server (Port 7860 bắt buộc cho HF Spaces)
port = int(os.environ.get("PORT", 7860))
app.run(host='0.0.0.0', port=port, threaded=True)