File size: 7,543 Bytes
9af2e8d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | 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, '<').replace(/>/g, '>');
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) |