Unbeatable / bot.js
SlnDmry's picture
Update bot.js
160beff verified
Raw
History Blame Contribute Delete
36.1 kB
const mineflayer = require("mineflayer");
const mineflayerViewer = require('prismarine-viewer').mineflayer;
const Movements = require("mineflayer-pathfinder").Movements;
const pathfinder = require("mineflayer-pathfinder").pathfinder;
const { GoalBlock, GoalXZ } = require("mineflayer-pathfinder").goals;
const util = require("util");
const { SocksClient } = require('socks');
const { Client } = require('ssh2');
const dns = require('dns').promises;
const config = require("./settings.json");
const loggers = require("./logging.js");
const logger = loggers.logger;
const https = require('https');
// --- AYARLAR ---
const HOME_ARRIVAL_DELAY_MS = 2000;
const POST_ACTION_DELAY_MS = 4000;
const WINDOW_OPEN_TIMEOUT_MS = 6000;
const DEFAULT_CHECK_INTERVAL_MS = 10 * 60 * 1000; // Varsayılan 10 dk
const FULL_FEED_COOLDOWN_MS = 50 * 60 * 1000; // Besleme sonrası sabit 50 dk
const GO_HOME_COMMAND = "/home";
const RETURN_ISLAND_COMMAND = "/is home";
const BESLEYICI_NAMES = (config.utils && config.utils["besleyici-names"]) || ["Besleyici", "ʙᴇsʟᴇʏiᴄi"];
let viewerInstance = null;
let viewerTimeout = null;
let bot = null;
let botIsReady = false;
let baglantiTuru = "Yok";
let activeMessages = [];
let messagesTimeout = null;
let isListeningChat = false;
const ansiToHtmlConvert = new (require('ansi-to-html'))({ newline: true });
// --- GÜVENLİ BAĞLANTI DEĞİŞKENLERİ ---
let isConnecting = false;
let isDestroying = false;
let reconnectTimeout = null;
let currentSshClient = null;
let currentStream = null;
let currentSocket = null;
let windowOpenResolvers = [];
// --- PANEL METRİKLERİ ---
let botUptimeStart = null;
let lastFullFeedTimeStr = "Henüz besleme yapılmadı";
let lastCheckTimeStr = "Henüz kontrol yapılmadı";
let nextScheduledCheckTimeStr = "Botun spawn olması bekleniyor...";
// --- DİNAMİK IP VE KONUM BİLGİLERİ ---
let activeConnectionIp = "Bilinmiyor";
let activeConnectionLocation = "Bilinmiyor";
let localExternalIp = "Bilinmiyor";
// --- DİNAMİK ZAMANLAYICI KONTROLÜ ---
let periodicTimeoutHandle = null;
async function getIpLocation(ip) {
if (!ip || ip === "Bilinmiyor" || ip.startsWith("127.")) return "Bilinmiyor";
// Denenecek servisler listesi
const services = [
`https://ip-api.com/json/${ip}?fields=status,country,city`,
`https://ipapi.co/${ip}/json/`
];
for (const url of services) {
try {
const location = await new Promise((resolve) => {
https.get(url, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const geo = JSON.parse(data);
// Servise göre cevap yapısı değişebilir, burada basit bir kontrol yapıyoruz
if (geo.country && geo.status !== "fail") {
resolve(`${geo.country} / ${geo.city || geo.region || ""}`);
} else {
resolve(null); // Bir sonraki servise geç
}
} catch (e) { resolve(null); }
});
}).on('error', () => resolve(null));
});
if (location) return location;
} catch (e) { continue; }
}
return "Konum Alınamadı";
}
https.get('https://api.ipify.org?format=json', (res) => {
let data = '';
res.on('data', chunk => { data += chunk; });
res.on('end', () => {
try {
const ipInfo = JSON.parse(data);
localExternalIp = ipInfo.ip;
console.log(`Dış IP adresin: ${localExternalIp}`);
} catch (err) {}
});
}).on('error', () => {});
async function createBot() {
if (isConnecting) {
logger.warn("[Sistem] Zaten aktif bir bağlantı süreci yürütülüyor. Mükerrer istek engellendi.");
return;
}
isConnecting = true;
isDestroying = false;
const net = require('net');
const sshClient = new Client();
currentSshClient = sshClient;
const targetServerIp = config.server.ip;
const targetServerPort = config.server.port;
const currentBotUsername = config["bot-account"]["username"];
bot = mineflayer.createBot({
username: currentBotUsername,
auth: config["bot-account"]["type"],
host: targetServerIp,
port: targetServerPort,
version: config.server.version,
connect: async (client) => {
let fallbackTriggered = false;
let isFinalFailureHandled = false;
const handleFinalFailure = (reason) => {
if (isFinalFailureHandled) return;
isFinalFailureHandled = true;
logger.error(`[Kritik Hata] ${reason}. Bağlantı sağlanamadı! Sistem en başa döndürülüyor...`);
baglantiTuru = "Yok";
botIsReady = false;
botUptimeStart = null;
activeConnectionIp = "Bilinmiyor";
activeConnectionLocation = "Bilinmiyor";
stopPeriodicHomeChecks();
safeDestroyAllConnections();
if (config.utils && config.utils["auto-reconnect"]) {
triggerSafeReconnect();
}
};
const connectDirectly = async (reason) => {
if (fallbackTriggered) return;
fallbackTriggered = true;
logger.warn(`[Bağlantı Yedekleme] Doğrudan IP moduna geçiliyor (Sebep: ${reason}). Kendi (Yurt dışı) IP'miz üzerinden GERÇEK TCP bağlantısı kuruluyor...`);
if (sshClient) {
try {
sshClient.removeAllListeners('ready');
sshClient.removeAllListeners('error');
sshClient.destroy();
} catch (e) {}
}
activeConnectionIp = localExternalIp !== "Bilinmiyor" ? localExternalIp : "Kendi Dış IP'niz";
activeConnectionLocation = await getIpLocation(localExternalIp);
const socket = net.connect({
host: targetServerIp,
port: parseInt(targetServerPort) || 25565
});
currentSocket = socket;
let isDirectHandled = false;
const directTimeout = setTimeout(() => {
if (isDirectHandled) return;
isDirectHandled = true;
socket.destroy();
handleFinalFailure("Kendi IP bağlantısı 1 dakikalık süre içinde zaman aşımına uğradı (Sunucu yanıt vermedi)");
}, 60000);
socket.on('connect', () => {
if (isDirectHandled) return;
isDirectHandled = true;
clearTimeout(directTimeout);
logger.info("[Bağlantı Yedekleme] Kendi IP'miz üzerinden GERÇEK TCP bağlantısı başarıyla kuruldu!");
baglantiTuru = "Normal";
if (client && typeof client.setSocket === 'function') {
client.setSocket(socket);
client.emit('connect');
}
});
socket.on('error', (err) => {
if (isDirectHandled) return;
isDirectHandled = true;
clearTimeout(directTimeout);
logger.error(`[Bağlantı Yedekleme Hatası] Sunucu bağlantıyı reddetti veya olumsuz yanıt verdi: ${err.message}`);
handleFinalFailure(`Kendi IP hatası: ${err.message}`);
});
socket.on('close', () => {
if (isDirectHandled) return;
isDirectHandled = true;
clearTimeout(directTimeout);
handleFinalFailure("Normal TCP soketi kapandı");
});
};
const githubRawUrl = 'https://raw.githubusercontent.com/suvarileader/che/refs/heads/main/doc/ower/doc.json';
logger.info("[GitHub Config] Güncel SSH tünel bilgileri GitHub'dan çekiliyor...");
let remoteData = null;
try {
remoteData = await new Promise((resolve, reject) => {
const reqTimeout = setTimeout(() => {
reject(new Error("GitHub bağlantısı 6 saniye içinde yanıt vermedi (Timeout)"));
}, 6000);
https.get(githubRawUrl, (res) => {
clearTimeout(reqTimeout);
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(body));
} catch (e) {
reject(new Error("JSON Ayrıştırma Hatası"));
}
});
}).on('error', (err) => {
clearTimeout(reqTimeout);
reject(err);
});
});
} catch (err) {
connectDirectly(`GitHub verisi alınamadı veya sayfa çöktü (${err.message})`);
return;
}
let sshConfig = null;
if (remoteData && Array.isArray(remoteData)) {
const myConfig = remoteData.find(item => item && item.account === currentBotUsername);
if (myConfig) {
sshConfig = {
host: (myConfig.serverName && myConfig.serverName !== "empty") ? myConfig.serverName : null,
port: 22,
username: (myConfig.username && myConfig.username !== "empty") ? myConfig.username : null,
password: (myConfig.pass && myConfig.pass !== "empty") ? myConfig.pass : config.utils["auto-auth"].password
};
if (!sshConfig.host || !sshConfig.username) {
sshConfig = null;
}
}
}
if (!sshConfig) {
connectDirectly(`GitHub listesinde '${currentBotUsername}' botu ile uyumlu geçerli SSH verisi bulunamadı`);
return;
}
logger.info(`[SSH Tünel] GitHub'dan alınan ${sshConfig.host} sunucusuna (${sshConfig.username}) bağlanılıyor...`);
const connectionTimeout = setTimeout(() => {
connectDirectly("SSH Bağlantı Zaman Aşımı (10 saniye yanıt alınamadı)");
}, 10000);
sshClient.on('ready', async () => {
clearTimeout(connectionTimeout);
logger.info('[SSH Tünel] SSH bağlantısı başarılı. Minecraft sunucusuna tünel açılıyor...');
try {
const lookup = await dns.lookup(sshConfig.host);
activeConnectionIp = lookup.address;
} catch (e) {
activeConnectionIp = sshConfig.host;
}
activeConnectionLocation = await getIpLocation(activeConnectionIp);
sshClient.forwardOut(
'127.0.0.1',
12345,
targetServerIp,
parseInt(targetServerPort) || 25565,
(err, stream) => {
if (err) {
connectDirectly(`Tünel yönlendirme hatası: ${err.message}`);
return;
}
logger.info("[SSH Tünel] Tünel başarıyla kuruldu! Mineflayer sokete bağlanıyor.");
baglantiTuru = "SSH";
currentStream = stream;
stream.once('close', () => {
if (!fallbackTriggered && !isDestroying) {
connectDirectly("Tünel stream kapandı");
}
});
stream.once('error', (e) => {
if (!fallbackTriggered && !isDestroying) {
connectDirectly(`Stream hatası: ${e.message}`);
}
});
if (client && typeof client.setSocket === 'function') {
client.setSocket(stream);
client.emit('connect');
}
}
);
});
sshClient.on('error', (err) => {
clearTimeout(connectionTimeout);
if (!fallbackTriggered && !isDestroying) {
connectDirectly(`SSH Sunucu Hatası: ${err.message}`);
}
});
try {
sshClient.connect(sshConfig);
} catch (err) {
clearTimeout(connectionTimeout);
connectDirectly(`Sistem Hatası: ${err.message}`);
}
}
});
bot.loadPlugin(pathfinder);
let lastFullFeedAt = 0;
let activeTimeouts = [];
function setSafeTimeout(fn, delay) {
const t = setTimeout(fn, delay);
activeTimeouts.push(t);
return t;
}
function stopPeriodicHomeChecks() {
if (periodicTimeoutHandle) {
clearTimeout(periodicTimeoutHandle);
periodicTimeoutHandle = null;
}
activeTimeouts.forEach(t => clearTimeout(t));
activeTimeouts = [];
}
function getExtendedDateTimeString() {
const now = new Date();
const dateStr = now.toLocaleDateString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric' });
const timeStr = now.toLocaleTimeString('tr-TR');
return `${dateStr} - ${timeStr}`;
}
function getFutureDateTimeString(delayMs) {
const future = new Date(Date.now() + delayMs);
return future.toLocaleTimeString('tr-TR');
}
function stripColorCodes(s) {
if (!s || typeof s !== "string") return s;
return s.replace(/§[0-9a-fk-or]/gi, "");
}
function safeDisplayNameToString(e) {
try {
if (e && e.displayName && typeof e.displayName.toString === "function") {
return e.displayName.toString();
}
} catch (err) {}
return null;
}
function getEntityReadableName(e) {
if (!e) return "";
const disp = safeDisplayNameToString(e);
if (disp) return stripColorCodes(disp);
if (e.name && typeof e.name === "string") return stripColorCodes(e.name);
if (e.username && typeof e.username === "string") return stripColorCodes(e.username);
return "";
}
function normalizeForMatch(s) {
if (!s || typeof s !== "string") return "";
let t = stripColorCodes(s);
if (t.normalize) t = t.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
return t.toLowerCase();
}
const TARGET_NAME_NORMS = BESLEYICI_NAMES.map(n => normalizeForMatch(n));
function isBesleyiciName(name) {
if (!name) return false;
const norm = normalizeForMatch(name);
return TARGET_NAME_NORMS.some(t => norm.includes(t));
}
function safeGet(obj, pathArray) {
try {
let cur = obj;
for (const p of pathArray) {
if (cur == null) return undefined;
cur = cur[p];
}
return cur;
} catch (e) { return undefined; }
}
function deepSearchForString(root, targetLower) {
const results = [];
const visited = new WeakSet();
function recurse(obj, path) {
if (obj == null || typeof obj === "function") return;
if (typeof obj === "string") {
const norm = normalizeForMatch(obj);
if (norm.includes(targetLower)) results.push({ path, value: obj });
return;
}
if (typeof obj !== "object") return;
if (visited.has(obj)) return;
visited.add(obj);
for (const key of Object.keys(obj)) {
recurse(obj[key], `${path}.${key}`);
}
}
try { recurse(root, "root"); } catch (e) {}
return results;
}
function parseWindowTitleToString(titleObj) {
try {
if (!titleObj) return "";
if (typeof titleObj === "string") return titleObj;
const s = titleObj.toString();
if (s && s !== "[object Object]") return s;
return JSON.stringify(titleObj);
} catch (err) { return ""; }
}
function extractMinyonCanFromSlot(slot) {
try {
if (!slot) return null;
function tryParseLoreText(text) {
try {
if (!text || typeof text !== "string") return null;
let plain = text;
try {
const maybe = JSON.parse(text);
if (maybe && typeof maybe === "object") {
if (maybe.extra && Array.isArray(maybe.extra) && maybe.extra.length > 0) {
if (typeof maybe.extra[0].text === "string") plain = maybe.extra[0].text;
} else if (typeof maybe.text === "string") {
plain = maybe.text;
}
}
} catch (e) {}
const xyMatch = plain.match(/(\d{1,4})\s*\/\s*(\d{1,4})/);
if (xyMatch) return Number(xyMatch[1]);
const numMatch = plain.match(/(\d{1,4})/);
if (numMatch) return Number(numMatch[1]);
return null;
} catch (e) { return null; }
}
if (slot.nbt) {
const lore = safeGet(slot.nbt, ["value", "display", "value", "Lore", "value"]);
if (Array.isArray(lore)) {
for (const line of lore) {
const res = tryParseLoreText(line.value || line);
if (res != null) return res;
}
}
}
const full = util.inspect(slot, { depth: null });
return tryParseLoreText(full);
} catch (err) { return null; }
}
async function goHome() {
if (!bot || !botIsReady) return;
logger.info(`[${currentBotUsername}] ${GO_HOME_COMMAND} komutu gönderiliyor.`);
bot.chat(GO_HOME_COMMAND);
}
async function returnIsland() {
if (!bot || !botIsReady) return;
logger.info(`[${currentBotUsername}] ${RETURN_ISLAND_COMMAND} komutu gönderiliyor.`);
bot.chat(RETURN_ISLAND_COMMAND);
}
// --- DİNAMİK PLANLAMA MOTORU ---
function scheduleNextCheck(delayMs) {
if (periodicTimeoutHandle) {
clearTimeout(periodicTimeoutHandle);
}
nextScheduledCheckTimeStr = `${getFutureDateTimeString(delayMs)} (${Math.round(delayMs / 60 / 1000)} dk sonra)`;
logger.info(`[Planlayıcı] Bir sonraki döngü planlandı. Zaman: ${nextScheduledCheckTimeStr}`);
periodicTimeoutHandle = setTimeout(async () => {
if (!bot || !botIsReady) return;
await goHome();
await new Promise(r => setTimeout(r, HOME_ARRIVAL_DELAY_MS));
await scanAndFeed();
await new Promise(r => setTimeout(r, POST_ACTION_DELAY_MS));
await returnIsland();
}, delayMs);
}
async function scanAndFeed() {
if (!bot || !botIsReady) return;
// Sunucu lagı yüzünden döngü üst üste tetiklenirse koruma
if (Date.now() < (lastFullFeedAt + 30000)) {
logger.warn("[Planlayıcı] Çok kısa süre önce işlem yapıldı. Güvenlik kilidi devrede.");
return;
}
const arr = Object.values(bot.entities);
let best = null;
for (const e of arr) {
const name = getEntityReadableName(e);
if (isBesleyiciName(name)) {
best = { entity: e, path: "displayName/name" };
break;
}
for (const target of TARGET_NAME_NORMS) {
const deep = deepSearchForString(e, target);
if (deep.length > 0) {
best = { entity: e, path: deep[0].path };
break;
}
}
if (best) break;
}
if (!best) {
logger.info(`[${currentBotUsername}] /home sonrası tarama: besleyici minyon bulunamadı.`);
bot.chat("/skyblock");
// Okuyamadığımız için 10 dakika sonra tekrar deneyecek şekilde planla
scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
return;
}
const dist = bot.entity.position.distanceTo(best.entity.position);
logger.info(`[${currentBotUsername}] '${best.entity.name || 'Armor Stand'}' bulundu id:${best.entity.id} path:${best.path} dist:${dist.toFixed(2)} — aktive ediliyor.`);
if (dist > 7) {
logger.warn(`safeActivateEntityPreferAt: entity id:${best.entity.id} uzak (${dist.toFixed(2)}). Max 7 blok.`);
logger.warn(`[${currentBotUsername}] 'Besleyici' aktive edilemedi (id:${best.entity.id}).`);
scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
return;
}
try {
if (bot.lookAt) await bot.lookAt(best.entity.position.offset(0, 1.0, 0), true);
await new Promise(r => setTimeout(r, 400));
const startTime = Date.now();
const endTime = startTime + WINDOW_OPEN_TIMEOUT_MS;
let clickCount = 0;
let isLoopActive = true;
await new Promise((resolve) => {
const cleanFinish = () => {
isLoopActive = false;
clearInterval(clickInterval);
windowOpenResolvers = [];
resolve();
};
windowOpenResolvers.push(() => {
cleanFinish();
});
const sendClickPacket = () => {
if (!isLoopActive) return;
if (bot.currentWindow != null) {
cleanFinish();
return;
}
if (Date.now() > endTime) {
logger.warn(`[Tıklama Sistemi] ${WINDOW_OPEN_TIMEOUT_MS} ms doldu ancak menü sunucudan gelmedi. Pes ediliyor.`);
cleanFinish();
scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
return;
}
clickCount++;
logger.info(`[Tıklama Sistemi] Menü tespiti başarısız. Deneme #${clickCount} gönderiliyor... (id:${best.entity.id})`);
try {
if (typeof bot.activateEntityAt === "function") bot.activateEntityAt(best.entity, best.entity.position);
else if (typeof bot.activateEntity === "function") bot.activateEntity(best.entity);
} catch (err) {}
};
sendClickPacket();
const clickInterval = setInterval(sendClickPacket, 600);
});
} catch (err) {}
}
bot.once("spawn", async () => {
isConnecting = false;
if (!bot) return;
bot.pathfinder.setMovements(new Movements(bot));
logger.info("Bot sunucuya giriş yaptı.");
botIsReady = true;
botUptimeStart = Date.now();
if (config.utils && config.utils["auto-auth"] && config.utils["auto-auth"].enabled) {
const password = config.utils["auto-auth"].password;
setSafeTimeout(() => {
if (bot && botIsReady) {
bot.chat(`/login ${password}`);
logger.info(`[${currentBotUsername}] Login komutu kullanıldı.`);
}
}, 2500);
setSafeTimeout(() => {
if (bot && botIsReady) {
bot.chat(`/skyblock`);
logger.info(`[${currentBotUsername}] Skyblock'a ışınlanma komutu kullanıldı.`);
}
}, 7500);
// İlk çalıştırma: Girişten 12.5 saniye sonra ilk kontrolü yap
setSafeTimeout(async () => {
if (!bot || !botIsReady) return;
await goHome();
await new Promise(r => setTimeout(r, HOME_ARRIVAL_DELAY_MS));
await scanAndFeed();
await new Promise(r => setTimeout(r, POST_ACTION_DELAY_MS));
await returnIsland();
}, 12500);
}
});
bot.on("windowOpen", async (window) => {
if (!bot) return;
const title = parseWindowTitleToString(window.title).toLowerCase();
if (!title.includes("besleyici") && !parseWindowTitleToString(window.title).includes("ʙᴇsʟᴇʏiᴄi")) {
logger.info(`[${currentBotUsername}] Bi chest açıldı ama window title ı uyuşmadığı için kapatıldı. ${parseWindowTitleToString(window.title)}`);
try { bot.closeWindow(window); } catch(e){}
return;
}
const beefSlot = window.slots.find(s => s?.name === "cooked_beef");
const can = extractMinyonCanFromSlot(beefSlot);
if (can != null) {
const currentStamp = getExtendedDateTimeString();
lastCheckTimeStr = `${currentStamp} (Okunan Can: ${can})`;
if (can <= 2) {
// CAN 2 VEYA ALTINDA: BESLE VE SABİT 50 DK PLANLA
try {
logger.info(`[${currentBotUsername}] Minyon canı ${can} olduğu için besleniyor...`);
await bot.clickWindow(36, 0, 0);
lastFullFeedAt = Date.now();
lastFullFeedTimeStr = `${currentStamp} (Minyon canı ${can} iken başarıyla beslendi)`;
logger.info(`[${currentBotUsername}] Tıklama onaylandı. Fixed 50dk bekleme aktif.`);
} catch(e) {
logger.error(`[Arayüz Hatası] Tıklama işlemi sunucu tarafından onaylanmadı: ${e.message}`);
}
scheduleNextCheck(FULL_FEED_COOLDOWN_MS);
} else {
// CAN 3 VEYA DAHA YÜKSEK: DİNAMİK PLANLAMA YAP
// Formül: (Can - 2) * 15 dakika. Maksimum 65 dakika.
let calculatedMinutes = (can - 2) * 15;
if (calculatedMinutes > 50) calculatedMinutes = 65;
if (calculatedMinutes < 15) calculatedMinutes = 15;
const dynamicDelayMs = calculatedMinutes * 60 * 1000;
logger.info(`[${currentBotUsername}] Minyon canı ${can} olarak okundu. Canın 2'ye düşmesi için ${calculatedMinutes} dakika beklenecek.`);
if (can >= 10) {
lastFullFeedTimeStr = `${currentStamp} (Canı ${can} olduğu için besleme gerekmedi)`;
}
scheduleNextCheck(dynamicDelayMs);
}
} else {
// Can okunamadıysa güvenlik amacıyla 10 dk sonra tekrar kontrol et
logger.warn(`[${currentBotUsername}] Menü açıldı ama can okunamadı. 10 dk sonra tekrar denenecek.`);
scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
}
try { bot.closeWindow(window); } catch(e){}
while (windowOpenResolvers.length > 0) {
const fn = windowOpenResolvers.shift();
if (typeof fn === 'function') fn();
}
});
bot.on("end", () => {
logger.warn("Bağlantı koptu. Tüm zamanlayıcılar sıfırlanıyor ve YENİDEN BAĞLANMA SÜRECİ BAŞLIYOR...");
stopPeriodicHomeChecks();
botIsReady = false;
baglantiTuru = "Yok";
botUptimeStart = null;
isListeningChat = false;
activeConnectionIp = "Bilinmiyor";
activeConnectionLocation = "Bilinmiyor";
nextScheduledCheckTimeStr = "Bağlantı koptuğu için planlama iptal edildi.";
safeDestroyAllConnections();
if (config.utils && config.utils["auto-reconnect"]) {
triggerSafeReconnect();
}
});
bot.on("kicked", (reason) => logger.warn(`Bot sunucudan atıldı: ${util.inspect(reason)}`));
bot.on("error", (err) => logger.error(`Mineflayer Hatası: ${err}`));
}
function safeDestroyAllConnections() {
if (isDestroying) return;
isDestroying = true;
isConnecting = false;
windowOpenResolvers = [];
if (viewerInstance && typeof viewerInstance.close === 'function') {
try {
if (viewerInstance.ws && typeof viewerInstance.ws.close === 'function') {
viewerInstance.ws.close();
}
viewerInstance.close();
} catch(e){}
}
viewerInstance = null;
if (bot) { bot.viewer = null; }
if (currentStream && typeof currentStream.destroy === 'function') {
try { currentStream.destroy(); } catch(e){}
}
if (currentSshClient && typeof currentSshClient.destroy === 'function') {
try { currentSshClient.destroy(); } catch(e){}
} else {
if (currentSocket && typeof currentSocket.destroy === 'function') {
try { currentSocket.destroy(); } catch(e){}
}
}
currentStream = null;
currentSocket = null;
currentSshClient = null;
if (bot) {
try {
bot.removeAllListeners('message');
bot.removeAllListeners('spawn');
bot.removeAllListeners('windowOpen');
bot.removeAllListeners('end');
bot.quit();
} catch(e){}
bot = null;
}
}
function triggerSafeReconnect() {
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
}
const delay = (config.utils && config.utils["auto-reconnect-delay"]) || 5000;
logger.info(`[Sistem] ${delay} ms sonra tekil bir hat üzerinden sistem sıfırdan başlatılacak...`);
reconnectTimeout = setTimeout(() => {
createBot();
}, delay);
}
function globalMinecraftMessageListener(jsonMsg) {
try {
if (!bot) return;
const chatMessage = bot.chat.ChatMessage ? new bot.chat.ChatMessage(jsonMsg) : jsonMsg;
const coloredAnsi = typeof chatMessage.toAnsi === 'function' ? chatMessage.toAnsi() : chatMessage.toString();
const htmlMessage = ansiToHtmlConvert.toHtml(coloredAnsi);
const timeStr = new Date().toLocaleTimeString('tr-TR');
activeMessages.push({ time: timeStr, text: htmlMessage });
if (activeMessages.length > 50) {
activeMessages.shift();
}
} catch (err) {
logger.error("Mesaj işlenirken hata oluştu: " + err.message);
}
}
createBot();
// --- EXPRESS SUNUCUSU VE PANEL ALTYAPISI ---
const express = require("express");
const app = express();
const http = require('http');
app.get("/status", (req, res) => {
let uptimeString = "Bağlı Değil";
if (botIsReady && botUptimeStart) {
const diffMs = Date.now() - botUptimeStart;
const diffSecs = Math.floor(diffMs / 1000);
const days = Math.floor(diffSecs / 86400);
const hours = Math.floor((diffSecs % 86400) / 3600);
const mins = Math.floor((diffSecs % 3600) / 60);
const secs = diffSecs % 60;
uptimeString = `${days} Gün, ${hours} Saat, ${mins} Dakika, ${secs} Saniye`;
}
res.send({
ready: botIsReady,
username: bot ? bot.username : null,
connection_type: baglantiTuru,
connection_ip: activeConnectionIp,
connection_geo: activeConnectionLocation,
bot_uptime: uptimeString,
last_minyon_check: lastCheckTimeStr,
last_minyon_feed: lastFullFeedTimeStr,
next_scheduled_check: nextScheduledCheckTimeStr // Panel için sonraki kontrol saati
});
});
app.get("/", (req, res) => res.send("Bot Aktif"));
app.get("/messages", (req, res) => {
if (messagesTimeout) {
clearTimeout(messagesTimeout);
messagesTimeout = null;
}
if (!bot || !botIsReady) {
return res.status(503).send("Bot hazır olmadığı için mesajlar dinlenemiyor.");
}
if (!isListeningChat) {
isListeningChat = true;
bot.on('message', globalMinecraftMessageListener);
logger.info("[Sohbet Tasarrufu] Panel aktif edildi. Minecraft mesaj dinleyicisi BAŞLATILDI.");
}
res.send({
server_time: new Date().toLocaleTimeString('tr-TR'),
logs: activeMessages
});
messagesTimeout = setTimeout(() => {
activeMessages = [];
messagesTimeout = null;
if (bot && isListeningChat) {
bot.removeListener('message', globalMinecraftMessageListener);
isListeningChat = false;
logger.info("[Sohbet Tasarrufu] 1 dakikadır izlenmiyor. Minecraft mesaj dinleyicisi DURDURULDU ve havuz temizlendi.");
}
}, 60000);
});
app.get("/chat", (req, res) => {
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Canlı Minecraft Sohbeti</title>
<style>
body { background-color: #111; color: #eee; font-family: 'Courier New', Courier, monospace; padding: 20px; margin: 0; }
.container { max-width: 900px; margin: 0 auto; background: #1a1a1a; padding: 15px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.5); }
h2 { margin-top: 0; border-bottom: 1px solid #333; padding-bottom: 10px; color: #fff; }
#chat-box { height: 500px; overflow-y: auto; background: #000; border: 1px solid #333; padding: 10px; border-radius: 4px; display: flex; flex-direction: column; }
.msg-line { margin-bottom: 6px; line-height: 1.4; font-size: 14px; word-break: break-all; }
.time-tag { color: #555; margin-right: 8px; font-size: 12px; }
</style>
</head>
<body>
<div class="container">
<h2>Canlı Oyun Sohbeti</h2>
<div id="chat-box"></div>
</div>
<script>
async function updateChat() {
try {
const res = await fetch('/messages');
if (!res.ok) return;
const data = await res.json();
const chatBox = document.getElementById('chat-box');
chatBox.innerHTML = '';
if (data.logs && data.logs.length > 0) {
data.logs.forEach(msg => {
const div = document.createElement('div');
div.className = 'msg-line';
div.innerHTML = '<span class="time-tag">[' + msg.time + ']</span>' + msg.text;
chatBox.appendChild(div);
});
chatBox.scrollTop = chatBox.scrollHeight;
} else {
chatBox.innerHTML = '<div style="color:#555;">Henüz yeni mesaj yok veya havuz temizlendi...</div>';
}
} catch(e) {}
}
setInterval(updateChat, 2000);
updateChat();
</script>
</body>
</html>
`;
res.send(html);
});
const checkAndStartViewer = (req, res, next) => {
if (viewerTimeout) {
clearTimeout(viewerTimeout);
viewerTimeout = null;
}
const isBotReady = bot && botIsReady;
if (!viewerInstance && isBotReady) {
try {
mineflayerViewer(bot, { port: 3000, firstPerson: true });
viewerInstance = bot.viewer;
logger.info("[Viewer] 3D Harita motoru arka planda (3000) başlatıldı.");
setTimeout(() => next(), 600);
return;
} catch (err) {
logger.error("Görüntüleyici başlatılamadı: " + err);
}
}
if (!isBotReady && !viewerInstance) {
return res.status(503).send("Bot henüz sunucuya giriş yapmadığı için ekran görüntülenemiyor.");
}
next();
};
app.use('/3dview', checkAndStartViewer, (req, res) => {
if (req.originalUrl === '/3dview' && !req.originalUrl.endsWith('/')) {
return res.redirect(301, '/3dview/');
}
const targetPath = req.url;
const proxyReq = http.request({
host: '127.0.0.1',
port: 3000,
path: targetPath,
method: req.method,
headers: req.headers
}, (proxyRes) => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res, { end: true });
});
proxyReq.on('error', (err) => {
logger.error("[Asset Hatası] Veri 3000 portundan çekilemedi: " + err.message);
if (!res.headersSent) res.status(502).send("Harita yükleniyor, lütfen sayfayı yenileyin.");
});
req.pipe(proxyReq, { end: true });
});
app.get("/restart", (req, res) => {
logger.info("[Sistem] Web paneli üzerinden tam restart isteği geldi. Sistem felç ediliyor...");
if (config.utils) {
config.utils["auto-reconnect"] = false;
}
isDestroying = true;
if (bot && botIsReady) {
try { bot.chat("Bot web paneli uzerinden tum kodlariyla yeniden baslatiliyor..."); } catch(e){}
res.send({ success: true, message: "Tüm proje kapatıldı. Konteyner birkaç saniye içinde sıfırdan başlayacak." });
setTimeout(() => {
try { bot.quit(); } catch(e){}
safeDestroyAllConnections();
logger.info("[Sistem] process.exit(0) çağrılıyor.");
process.exit(0);
}, 1000);
} else {
res.send({ success: true, message: "Bot bağlı değildi ancak konteyner kökten kapatılıyor." });
setTimeout(() => {
process.exit(0);
}, 1000);
}
});
const server = app.listen(process.env.PORT || 7860, () => {
logger.info("Ana Express sunucusu 7860 portunda aktif.");
});
server.on('upgrade', (req, socket, head) => {
if (req.url.includes('socket.io') || req.url.startsWith('/3dview')) {
if (viewerTimeout) {
clearTimeout(viewerTimeout);
viewerTimeout = null;
}
let targetWsPath = req.url;
if (targetWsPath.startsWith('/3dview')) {
targetWsPath = targetWsPath.replace('/3dview', '');
}
if (!targetWsPath.startsWith('/')) {
targetWsPath = '/' + targetWsPath;
}
const proxyReq = http.request({
host: '127.0.0.1',
port: 3000,
path: targetWsPath,
method: req.method,
headers: req.headers
});
proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
socket.write('HTTP/1.1 101 Switching Protocols\r\n' +
Object.keys(proxyRes.headers).map(key => `${key}: ${proxyRes.headers[key]}`).join('\r\n') +
'\r\n\r\n');
proxySocket.pipe(socket);
socket.pipe(proxySocket);
socket.on('close', () => {
if (viewerTimeout) clearTimeout(viewerTimeout);
viewerTimeout = setTimeout(() => {
if (viewerInstance && typeof viewerInstance.close === 'function') {
try {
if (viewerInstance.ws && typeof viewerInstance.ws.close === 'function') {
viewerInstance.ws.close();
}
viewerInstance.close();
if (bot) bot.viewer = null;
viewerInstance = null;
logger.info("[RAM Tasarrufu] 10 saniyedir kimse izlemiyor. Harita kapatıldı, RAM serbest!");
} catch (e) {
logger.error("Viewer kapatılırken hata: " + e.message);
}
}
}, 10000);
});
});
proxyReq.on('error', (err) => {
logger.error("[WS Tünel Hatası] Websocket aktarılamadı: " + err.message);
});
proxyReq.end();
}
});
const originalStderrWrite = process.stderr.write;
process.stderr.write = function (chunk, encoding, callback) {
const message = chunk.toString();
if (message.includes("Ignoring block entities as chunk failed to load")) {
if (typeof callback === 'function') callback();
return true;
}
return originalStderrWrite.apply(process.stderr, arguments);
};