const mineflayer = require("mineflayer"); const mineflayerViewer = require('prismarine-viewer').mineflayer; 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 getRecentLogs = loggers.getRecentLogs; const https = require('https'); // --- KÜRESEL GÜVENLİK AĞI --- process.on('unhandledRejection', (reason) => { logger.error(`[Küresel Güvenlik Ağı] Yakalanmamış Promise hatası (unhandledRejection): ${util.inspect(reason)}`); }); process.on('uncaughtException', (err) => { logger.error(`[Küresel Güvenlik Ağı] Yakalanmamış istisna (uncaughtException): ${err && err.stack ? err.stack : err}`); }); // --- AYARLAR --- const HOME_ARRIVAL_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_ISLAND_COMMAND = "/is go"; const BESLEYICI_NAMES_RAW = ["Besleyici", "ʙᴇsʟᴇʏiᴄi"]; const BESLEYICI_NAMES = Array.isArray(BESLEYICI_NAMES_RAW) ? BESLEYICI_NAMES_RAW : [BESLEYICI_NAMES_RAW]; const KAZICI_NAMES_RAW = (config.utils && (config.utils["kazici-names"] || config.utils["kazici-name"])) || ["Kazıcı", "ᴋᴀᴢıᴄı", "ᴋᴀᴢɪᴄɪ"]; const KAZICI_NAMES = Array.isArray(KAZICI_NAMES_RAW) ? KAZICI_NAMES_RAW : [KAZICI_NAMES_RAW]; const KAZICI_STORAGE_TRANSFER_SLOT = 17; const KAZICI_BETWEEN_DELAY_MS = 5 * 1000; const KAZICI_ACTION_SETTLE_DELAY_MS = 1200; // --- ÇİFTÇİ (HEPSİNİ SAT) MENÜSÜ İÇİN AYARLAR --- const CIFTCI_NAMES_RAW = (config.utils && (config.utils["ciftci-names"] || config.utils["ciftci-name"])) || ["Çiftçi", "çiꜰᴛçi"]; const CIFTCI_NAMES = Array.isArray(CIFTCI_NAMES_RAW) ? CIFTCI_NAMES_RAW : [CIFTCI_NAMES_RAW]; const CIFTCI_SELL_ALL_SLOT = 45; const CIFTCI_COMMAND = "/çiftçi menü"; const CIFTCI_WINDOW_TIMEOUT_MS = 6000; const CIFTCI_LORE_FALLBACK_KEYWORDS = ["hepsini sat"]; const CIFTCI_CLICK_DELAY_AFTER_OPEN_MS = 1000; const CIFTCI_CLICK_REPEAT_COUNT = 3; const CIFTCI_CLICK_REPEAT_GAP_MS = 350; const KAZICI_CYCLE_INTERVAL_MS = 5 * 60 * 1000; const CIFTCI_CYCLE_INTERVAL_MS = 60 * 1000; const CIFTCI_FILTER_MODES = [ "Standart", "Favoriler", "Depo dolumuna göre", "Jeneratör materyalleri", "Satışı açık olanlar", "Toplaması açık olanlar" ]; const CIFTCI_TARGET_FILTER_INDEX = 3; const CIFTCI_ACTIVE_FILTER_COLOR = "#FCF988"; const CIFTCI_FILTER_ITEM_NAME_KEYWORD = "Filtre"; const CIFTCI_FILTER_STEP_WAIT_MS = 1000; const CIFTCI_FILTER_RETRY_WAIT_MS = 1000; const CIFTCI_FILTER_MAX_RETRIES = 10; const STORAGE_SCAN_INTERVAL_MS = 15 * 60 * 1000; const STORAGE_TRACKED_ITEMS = ["diamond", "emerald", "iron_block"]; // --- SİPARİŞ (ORDER) SİSTEMİ AYARLARI --- const ORDER_COMMAND_PREFIX = "/order"; const ORDER_WINDOW_TIMEOUT_MS = 8000; const ORDER_WINDOW_SETTLE_DELAY_MS = 500; const ORDER_AFTER_SELECT_DELAY_MS = 500; const ORDER_IS_GO_SECOND_DELAY_MS = 5000; const ORDER_ALLOWED_ITEMS = ["diamond", "emerald"]; const ORDER_LIST_TITLE_KEYWORD = "ѕiᴘᴀʀişʟᴇʀ"; const ORDER_DELIVERY_TITLE_KEYWORD = "ᴛᴇѕʟiᴍ ᴇᴛᴍᴇ"; // Nick seçildikten sonra Teslim Etme penceresinin gerçekten açılmasını beklemek için // kullanılan zaman aşımı (sabit sleep yerine). const ORDER_DELIVERY_WINDOW_WAIT_MS = 8000; let storageInfoCache = { diamond: null, emerald: null, iron_block: null, lastUpdated: null }; let storageScanIntervalHandle = null; let ciftciWindowPurpose = "sell"; const STALL_CHECK_INTERVAL_MS = 15 * 1000; const STALL_TIMEOUT_MS = 60 * 1000; const FAILOVER_HOME_ARRIVAL_DELAY_MS = 4000; const FAILOVER_LOGIN_DELAY_MS = 2500; const FAILOVER_SKYBLOCK_DELAY_MS = 7500; const FAILOVER_ISGO_DELAY_MS = 12500; const FAILOVER_RECHECK_INTERVAL_MS = 20 * 1000; const FAILOVER_MAX_WAIT_MS = 60 * 60 * 1000; const FAILOVER_SPAWN_TIMEOUT_MS = 30 * 1000; const WATCHED_KAZICI_SKIN_HASHES = new Set([ "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDQ4ODcwY2E3N2FhNjk5ZDAxNzA5M2I4Mjc3OWQwYTU5OGRjNGYzZjQ5ZDM1MTUzMmFlNGQ5NjMzZGNjMmE1YSJ9fX0=", ]); 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 }); let isConnecting = false; let isDestroying = false; let reconnectTimeout = null; let currentSshClient = null; let currentStream = null; let currentSocket = null; let windowOpenResolvers = []; let botUptimeStart = null; let lastFullFeedTimeStr = "Henüz besleme yapılmadı"; let lastCheckTimeStr = "Henüz kontrol yapılmadı"; let nextScheduledCheckTimeStr = "Botun spawn olması bekleniyor..."; let lastKaziciCheckTimeStr = "Henüz kazıcı kontrolü yapılmadı"; let nextKaziciTimeStr = "Botun spawn olması bekleniyor..."; let nextKaziciAtMs = null; let lastCiftciSellTimeStr = "Henüz çiftçi satışı yapılmadı"; let nextCiftciTimeStr = "Botun spawn olması bekleniyor..."; let nextCiftciAtMs = null; let lastOrderTimeStr = "Henüz sipariş işlemi yapılmadı"; let lastCheckAtMs = null; let nextScheduledCheckAtMs = null; let kaziciIntervalHandle = null; let ciftciIntervalHandle = null; let isMainCycleRunning = false; let poseTracker = new Map(); let stallCheckIntervalHandle = null; let failoverModeActive = false; let failoverBot = null; let failoverInProgress = false; let activeConnectionIp = "Bilinmiyor"; let activeConnectionLocation = "Bilinmiyor"; let localExternalIp = "Bilinmiyor"; let periodicTimeoutHandle = null; let activeTimeouts = []; function setSafeTimeout(fn, delay) { const t = setTimeout(fn, delay); activeTimeouts.push(t); return t; } class PriorityGuiMutex { constructor() { this._locked = false; this._waiters = []; } isLocked() { return this._locked; } tryAcquire() { if (this._locked) return false; this._locked = true; return true; } acquire() { if (!this._locked) { this._locked = true; return Promise.resolve(); } return new Promise((resolve) => { this._waiters.push(resolve); }); } release() { if (this._waiters.length > 0) { const next = this._waiters.shift(); next(); } else { this._locked = false; } } } const guiMutex = new PriorityGuiMutex(); let botActions = { scanAndFeedManual: null, scanAndCollectMinersManual: null, openCiftciAndSellAllManual: null, goToIslandManual: null, sendSkyblockManual: null, antiBotMove: null, sendMoneyToNick: null, scanStorageInfoManual: null, processOrderManual: null }; function formatDurationMs(ms) { if (ms == null || isNaN(ms) || ms < 0) return "0 saniye"; let totalSeconds = Math.round(ms / 1000); const days = Math.floor(totalSeconds / 86400); totalSeconds -= days * 86400; const hours = Math.floor(totalSeconds / 3600); totalSeconds -= hours * 3600; const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds - minutes * 60; const parts = []; if (days > 0) parts.push(`${days} gün`); if (hours > 0) parts.push(`${hours} saat`); if (minutes > 0) parts.push(`${minutes} dakika`); if (seconds > 0 || parts.length === 0) parts.push(`${seconds} saniye`); return parts.join(" "); } function stopPeriodicHomeChecks() { if (periodicTimeoutHandle) { clearTimeout(periodicTimeoutHandle); periodicTimeoutHandle = null; } activeTimeouts.forEach(t => clearTimeout(t)); activeTimeouts = []; if (kaziciIntervalHandle) { clearInterval(kaziciIntervalHandle); kaziciIntervalHandle = null; logger.info("[Kazıcı Periyodik] Döngü zamanlayıcısı durduruldu."); } if (ciftciIntervalHandle) { clearInterval(ciftciIntervalHandle); ciftciIntervalHandle = null; logger.info("[Çiftçi Periyodik] Döngü zamanlayıcısı durduruldu."); } if (storageScanIntervalHandle) { clearInterval(storageScanIntervalHandle); storageScanIntervalHandle = null; logger.info("[Storage Tarama] Döngü zamanlayıcısı durduruldu."); } nextScheduledCheckAtMs = null; nextKaziciAtMs = null; nextCiftciAtMs = null; nextKaziciTimeStr = "Zamanlama durduruldu."; nextCiftciTimeStr = "Zamanlama durduruldu."; } let manualStopRequested = false; async function getIpLocation(ip) { if (!ip || ip === "Bilinmiyor" || ip.startsWith("127.")) return "Bilinmiyor"; 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); if (geo.country && geo.status !== "fail") { resolve(`${geo.country} / ${geo.city || geo.region || ""}`); } else { resolve(null); } } 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', () => {}); function getEntitySkinHash(entity) { try { const equipment = entity.equipment || []; for (const item of equipment) { if (item && item.name === "player_head" && item.components) { for (const comp of item.components) { if (comp && comp.type === "profile" && comp.data && Array.isArray(comp.data.properties)) { const texProp = comp.data.properties.find(p => p.name === "textures"); if (texProp && texProp.value) { return texProp.value; } } } } } } catch (e) {} return null; } function hasIronPickaxeEquipped(entity) { try { const equipment = entity.equipment || []; const mainHand = equipment[0]; return !!(mainHand && mainHand.name === "iron_pickaxe"); } catch (e) { return false; } } function isWatchedKaziciEntity(entity) { if (!entity || entity.name !== "armor_stand") return false; const skinHash = getEntitySkinHash(entity); if (!skinHash || !WATCHED_KAZICI_SKIN_HASHES.has(skinHash)) return false; return hasIronPickaxeEquipped(entity); } function getPoseKey(entity) { try { const md = entity.metadata || []; const parts = []; for (const m of md) { if (m && typeof m === "object" && "pitch" in m && "yaw" in m && "roll" in m) { parts.push(`${m.pitch.toFixed(2)},${m.yaw.toFixed(2)},${m.roll.toFixed(2)}`); } } if (entity.position) { parts.push(`${entity.position.x.toFixed(2)}_${entity.position.y.toFixed(2)}_${entity.position.z.toFixed(2)}`); } return parts.join("|"); } catch (e) { return null; } } function trackKaziciPoseIfWatched(entity) { if (!entity) return; if (!isWatchedKaziciEntity(entity)) return; const poseKey = getPoseKey(entity); if (poseKey == null) return; const prev = poseTracker.get(entity.id); if (!prev) { poseTracker.set(entity.id, { poseKey, lastChangeAt: Date.now(), uuid: entity.uuid }); return; } if (prev.poseKey !== poseKey) { prev.poseKey = poseKey; prev.lastChangeAt = Date.now(); } } function pruneStaleTrackedEntities() { if (!bot || !bot.entities) return; for (const id of poseTracker.keys()) { if (!bot.entities[id]) poseTracker.delete(id); } } function startStallWatcher() { if (stallCheckIntervalHandle) return; stallCheckIntervalHandle = setInterval(() => { if (!bot || !botIsReady || failoverModeActive) return; pruneStaleTrackedEntities(); const now = Date.now(); let anyStalled = false; for (const [id, info] of poseTracker.entries()) { if (now - info.lastChangeAt >= STALL_TIMEOUT_MS) { anyStalled = true; logger.warn(`[Failover] İzlenen kazıcı minyon (entityId:${id}) ${formatDurationMs(now - info.lastChangeAt)} boyunca hareketsiz.`); } } if (anyStalled && !failoverInProgress) { triggerFailoverSequence().catch(err => { logger.error(`[Failover Hatası] ${err && err.stack ? err.stack : err}`); failoverInProgress = false; }); } }, STALL_CHECK_INTERVAL_MS); logger.info("[Failover] Pose izleme döngüsü başlatıldı."); } function stopStallWatcher() { if (stallCheckIntervalHandle) { clearInterval(stallCheckIntervalHandle); stallCheckIntervalHandle = null; } poseTracker.clear(); } async function triggerFailoverSequence() { if (failoverInProgress) return; failoverInProgress = true; failoverModeActive = true; const mainUsername = config["bot-account"]["username"]; const backupUsername = `${mainUsername}1`; logger.warn(`[Failover] İzlenen kazıcı(lar) durdu. Ana hesap (${mainUsername}) geçici olarak düşürülüp yedek hesap (${backupUsername}) devreye alınıyor.`); manualStopRequested = true; stopPeriodicHomeChecks(); safeDestroyAllConnections(); try { await runBackupAccountAndWaitForRecovery(backupUsername); } catch (err) { logger.error(`[Failover] Yedek hesap akışında hata: ${err && err.stack ? err.stack : err}`); } logger.info(`[Failover] İzlenen kazıcı(lar) tekrar aktif (ya da azami bekleme süresi doldu). Ana hesap (${mainUsername}) yeniden bağlanıyor.`); manualStopRequested = false; failoverModeActive = false; poseTracker.clear(); createBot(); failoverInProgress = false; } function runBackupAccountAndWaitForRecovery(backupUsername) { return new Promise((resolve) => { const backupPoseTracker = new Map(); let recheckHandle = null; let safetyTimeoutHandle = null; let spawnTimeoutHandle = null; let finished = false; const finish = () => { if (finished) return; finished = true; if (recheckHandle) clearInterval(recheckHandle); if (safetyTimeoutHandle) clearTimeout(safetyTimeoutHandle); if (spawnTimeoutHandle) clearTimeout(spawnTimeoutHandle); try { if (failoverBot) { failoverBot.removeAllListeners(); failoverBot.quit(); } } catch (e) {} failoverBot = null; resolve(); }; try { failoverBot = mineflayer.createBot({ username: backupUsername, auth: config["bot-account"]["type"], host: config.server.ip, port: config.server.port, version: config.server.version, }); } catch (err) { logger.error(`[Failover - Yedek Hesap] Bot oluşturulamadı: ${err && err.message ? err.message : err}`); finish(); return; } spawnTimeoutHandle = setTimeout(() => { logger.error(`[Failover - Yedek Hesap] ${backupUsername} ${formatDurationMs(FAILOVER_SPAWN_TIMEOUT_MS)} içinde spawn olamadı, vazgeçiliyor (bir sonraki kontrolde tekrar denenecek).`); finish(); }, FAILOVER_SPAWN_TIMEOUT_MS); failoverBot.once("spawn", () => { clearTimeout(spawnTimeoutHandle); logger.info(`[Failover - Yedek Hesap] ${backupUsername} sunucuya girdi.`); setTimeout(() => { try { failoverBot.chat(`/login ${config.utils["auto-auth"].password}`); } catch (e) {} logger.info(`[Failover - Yedek Hesap] ${backupUsername} Login komutu kullanıldı.`); }, FAILOVER_LOGIN_DELAY_MS); setTimeout(() => { try { failoverBot.chat("/skyblock"); } catch (e) {} logger.info(`[Failover - Yedek Hesap] ${backupUsername} Skyblock'a ışınlanma komutu kullanıldı.`); }, FAILOVER_SKYBLOCK_DELAY_MS); setTimeout(() => { try { failoverBot.chat(GO_ISLAND_COMMAND); } catch (e) {} logger.info(`[Failover - Yedek Hesap] ${GO_ISLAND_COMMAND} gönderildi, izlenen minyonların tekrar hareket etmesi bekleniyor...`); }, FAILOVER_ISGO_DELAY_MS); setTimeout(() => { recheckHandle = setInterval(() => { try { const arr = Object.values(failoverBot.entities || {}); let allRecovered = true; let sawAnyWatched = false; for (const e of arr) { if (!isWatchedKaziciEntity(e)) continue; sawAnyWatched = true; const poseKey = getPoseKey(e); const prev = backupPoseTracker.get(e.id); if (!prev) { backupPoseTracker.set(e.id, { poseKey, lastChangeAt: Date.now() }); allRecovered = false; continue; } if (prev.poseKey !== poseKey) { prev.poseKey = poseKey; prev.lastChangeAt = Date.now(); } else if (Date.now() - prev.lastChangeAt < STALL_TIMEOUT_MS) { // ok } else { allRecovered = false; } } if (sawAnyWatched && allRecovered) { logger.info("[Failover - Yedek Hesap] İzlenen minyonlar tekrar hareket ediyor. Yedek hesaptan çıkılıyor."); finish(); } } catch (e) {} }, FAILOVER_RECHECK_INTERVAL_MS); }, FAILOVER_HOME_ARRIVAL_DELAY_MS + FAILOVER_ISGO_DELAY_MS); }); failoverBot.on("error", (err) => { logger.error(`[Failover - Yedek Hesap] Hata: ${err && err.message ? err.message : err}`); }); failoverBot.on("kicked", (reason) => { logger.warn(`[Failover - Yedek Hesap] Sunucudan atıldı: ${util.inspect(reason)}`); finish(); }); failoverBot.on("end", () => { if (!finished) { logger.warn("[Failover - Yedek Hesap] Bağlantı beklenmedik şekilde koptu."); finish(); } }); safetyTimeoutHandle = setTimeout(() => { logger.warn(`[Failover - Yedek Hesap] Azami bekleme süresi (${formatDurationMs(FAILOVER_MAX_WAIT_MS)}) doldu, ana hesaba geri dönülüyor.`); finish(); }, FAILOVER_MAX_WAIT_MS); }); } 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; try { 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 || isDestroying) 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(); lastCheckAtMs = null; safeDestroyAllConnections(); if (!manualStopRequested && config.utils && config.utils["auto-reconnect"]) { triggerSafeReconnect(); } }; const connectDirectly = async (reason) => { if (fallbackTriggered || isDestroying) 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ı"); }); }; try { 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 { try { const lookup = await dns.lookup(sshConfig.host); activeConnectionIp = lookup.address; } catch (e) { activeConnectionIp = sshConfig.host; } activeConnectionLocation = await getIpLocation(activeConnectionIp); let tunnelHandled = false; const tunnelTimeout = setTimeout(() => { if (tunnelHandled || isDestroying) return; tunnelHandled = true; connectDirectly("SSH tüneli (forwardOut) 10 saniye içinde açılamadı (sunucu/ağ lagı olabilir)"); }, 10000); sshClient.forwardOut( '127.0.0.1', 12345, targetServerIp, parseInt(targetServerPort) || 25565, (err, stream) => { if (tunnelHandled || isDestroying) return; tunnelHandled = true; clearTimeout(tunnelTimeout); 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'); } } ); } catch (err) { if (!isDestroying) { connectDirectly(`SSH 'ready' işleyicisinde beklenmeyen hata: ${err && err.message ? err.message : err}`); } } }); 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}`); } } catch (err) { logger.error(`[connect() Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`); if (!isDestroying) { handleFinalFailure(`connect() içinde yakalanmayan hata: ${err && err.message ? err.message : err}`); } } } }); let lastFullFeedAt = 0; 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, ""); try { return t.toLocaleLowerCase('tr-TR'); } catch (e) { return t.toLowerCase(); } } function normalizeVariants(s) { if (!s || typeof s !== "string") return [""]; let t = stripColorCodes(s); if (t.normalize) t = t.normalize("NFKD").replace(/[\u0300-\u036f]/g, ""); const variants = new Set(); try { variants.add(t.toLocaleLowerCase('tr-TR')); } catch (e) {} variants.add(t.toLowerCase()); return Array.from(variants); } function matchesAnyName(sourceString, targetVariantsList) { if (!sourceString) return false; const sourceVariants = normalizeVariants(sourceString); return sourceVariants.some(sv => targetVariantsList.some(t => sv.includes(t))); } const TARGET_NAME_NORMS = BESLEYICI_NAMES.flatMap(n => normalizeVariants(n)); function isBesleyiciName(name) { return matchesAnyName(name, TARGET_NAME_NORMS); } const TARGET_KAZICI_NORMS = KAZICI_NAMES.flatMap(n => normalizeVariants(n)); function isKaziciName(name) { return matchesAnyName(name, TARGET_KAZICI_NORMS); } const TARGET_CIFTCI_NORMS = CIFTCI_NAMES.flatMap(n => normalizeVariants(n)); function isCiftciName(name) { return matchesAnyName(name, TARGET_CIFTCI_NORMS); } 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 getComponentData(slot, type) { try { if (!slot || !Array.isArray(slot.components)) return undefined; const comp = slot.components.find(c => c && c.type === type); return comp ? comp.data : undefined; } catch (e) { return undefined; } } function nbtUnwrap(node) { if (node && typeof node === "object" && "type" in node && "value" in node) { return node.value; } return node; }; function flattenTextComponent(node) { if (node == null) return ""; if (typeof node === "string") return node; if (Array.isArray(node)) return node.map(flattenTextComponent).join(""); if (typeof node === "object") { if ("type" in node && "value" in node) { const t = node.type, v = node.value; if (t === "string") return typeof v === "string" ? v : ""; if (t === "list" || t === "compound") return flattenTextComponent(v); return ""; } let out = ""; if ("text" in node) out += flattenTextComponent(node.text); if ("extra" in node) out += flattenTextComponent(node.extra); return out; } return ""; }; function parseComponentLoreLine(lineNode) { let text = ""; let color = null; function walk(node) { if (node == null) return; if (typeof node === "string") { text += node; return; } if (Array.isArray(node)) { node.forEach(walk); return; } if (typeof node === "object") { if ("type" in node && "value" in node) { const t = node.type, v = node.value; if (t === "string") { text += (typeof v === "string" ? v : ""); return; } if (t === "list" || t === "compound") { walk(v); return; } return; } if ("color" in node) { const colVal = nbtUnwrap(node.color); if (color == null && typeof colVal === "string") color = colVal; } if ("text" in node) walk(node.text); if ("extra" in node) walk(node.extra); } } walk(lineNode); return { text: text.trim(), color }; }; function deepSearchForString(root, targetVariantsList) { const results = []; const visited = new WeakSet(); function recurse(obj, path) { if (obj == null || typeof obj === "function") return; if (typeof obj === "string") { if (matchesAnyName(obj, targetVariantsList)) 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; } } function getItemDisplayTexts(slot) { const texts = []; try { if (!slot) return texts; const customNameData = getComponentData(slot, "custom_name"); if (customNameData !== undefined) { const nameTxt = flattenTextComponent(customNameData); if (nameTxt) texts.push(nameTxt); } const loreData = getComponentData(slot, "lore"); if (Array.isArray(loreData)) { for (const line of loreData) { const { text } = parseComponentLoreLine(line); if (text) texts.push(text); } } if (texts.length > 0) return texts; function extractPlain(raw) { if (!raw || typeof raw !== "string") return null; try { const maybe = JSON.parse(raw); if (maybe && typeof maybe === "object") { if (maybe.extra && Array.isArray(maybe.extra)) { return maybe.extra.map(x => (x && typeof x.text === "string") ? x.text : "").join(""); } else if (typeof maybe.text === "string") { return maybe.text; } } } catch (e) {} return raw; } if (slot.nbt) { const name = safeGet(slot.nbt, ["value", "display", "value", "Name", "value"]); const plainName = extractPlain(name); if (plainName) texts.push(plainName); const lore = safeGet(slot.nbt, ["value", "display", "value", "Lore", "value"]); if (Array.isArray(lore)) { for (const line of lore) { const plainLine = extractPlain(line.value || line); if (plainLine) texts.push(plainLine); } } } if (texts.length === 0) { texts.push(util.inspect(slot, { depth: null })); } } catch (err) {} return texts; }; function windowMatchesLoreKeyword(window, keywords) { try { if (!window || !Array.isArray(window.slots)) return false; const normKeywords = keywords.flatMap(k => normalizeVariants(k)); for (const slot of window.slots) { if (!slot) continue; const texts = getItemDisplayTexts(slot); for (const t of texts) { if (matchesAnyName(t, normKeywords)) return true; } } } catch (e) {} return false; } async function goToIsland() { if (!bot || !botIsReady) return; try { logger.info(`[${currentBotUsername}] ${GO_ISLAND_COMMAND} komutu gönderiliyor.`); bot.chat(GO_ISLAND_COMMAND); } catch (err) { logger.error(`[goToIsland Hatası] Komut gönderilemedi: ${err && err.message ? err.message : err}`); } } async function sendSkyblockCommand() { if (!bot || !botIsReady) return; try { logger.info(`[${currentBotUsername}] /skyblock komutu (manuel) gönderiliyor.`); bot.chat("/skyblock"); } catch (err) { logger.error(`[sendSkyblockCommand Hatası] Komut gönderilemedi: ${err && err.message ? err.message : err}`); } } const MONEY_BALANCE_REGEX = /bakiyen:\s*([\d.,]+)\s*TL/i; const MONEY_RESPONSE_TIMEOUT_MS = 8000; function parseBalanceToAdjustedAmount(rawBalanceStr) { const noCommas = rawBalanceStr.replace(/,/g, ""); const beforeDot = noCommas.split(".")[0]; const intValue = parseInt(beforeDot, 10); if (isNaN(intValue)) return null; return intValue - 1000; } async function sendMoneyToNick(nick) { if (!bot || !botIsReady) { return { success: false, message: "Bot hazır değil." }; } if (!nick || typeof nick !== "string" || nick.trim().length === 0) { return { success: false, message: "nick parametresi zorunlu." }; } return new Promise((resolve) => { let finished = false; let timeoutHandle = null; const cleanup = () => { if (finished) return; finished = true; clearTimeout(timeoutHandle); bot.removeListener('message', onMessage); }; const onMessage = (jsonMsg) => { try { const chatMessage = bot.chat.ChatMessage ? new bot.chat.ChatMessage(jsonMsg) : jsonMsg; const text = typeof chatMessage.toString === 'function' ? chatMessage.toString() : String(jsonMsg); const plain = stripColorCodes(text); const match = plain.match(MONEY_BALANCE_REGEX); if (match) { const adjusted = parseBalanceToAdjustedAmount(match[1]); cleanup(); if (adjusted == null) { logger.error(`[Sendmoney] Bakiye ayrıştırılamadı: "${plain}"`); resolve({ success: false, message: "Bakiye mesajı ayrıştırılamadı." }); return; } try { const payCommand = `/pay ${nick} ${adjusted}`; bot.chat(payCommand); logger.info(`[Sendmoney] Bakiye okundu: ${match[1]} -> Gönderilecek: ${adjusted}. Komut: ${payCommand}`); resolve({ success: true, message: "Para gönderildi.", balance_raw: match[1], amount_sent: adjusted, nick }); } catch (err) { logger.error(`[Sendmoney] /pay komutu gönderilemedi: ${err && err.message ? err.message : err}`); resolve({ success: false, message: "/pay komutu gönderilirken hata oluştu." }); } } } catch (err) { logger.error(`[Sendmoney] Mesaj işlenirken hata: ${err && err.message ? err.message : err}`); } }; bot.on('message', onMessage); timeoutHandle = setTimeout(() => { cleanup(); logger.warn(`[Sendmoney] ${formatDurationMs(MONEY_RESPONSE_TIMEOUT_MS)} içinde bakiye mesajı gelmedi.`); resolve({ success: false, message: "Zaman aşımı: bakiye mesajı alınamadı." }); }, MONEY_RESPONSE_TIMEOUT_MS); try { bot.chat("/money"); logger.info(`[Sendmoney] /money komutu gönderildi, bakiye mesajı bekleniyor...`); } catch (err) { cleanup(); logger.error(`[Sendmoney] /money komutu gönderilemedi: ${err && err.message ? err.message : err}`); resolve({ success: false, message: "/money komutu gönderilirken hata oluştu." }); } }); } async function antiBotMove() { if (!bot || !botIsReady) return; try { const moves = ['forward', 'left', 'right', 'jump']; const moveCount = Math.floor(Math.random() * 4) + 2; for (let i = 0; i < moveCount; i++) { const move = moves[Math.floor(Math.random() * moves.length)]; const duration = 150 + Math.floor(Math.random() * 500); bot.setControlState(move, true); await new Promise(r => setTimeout(r, duration)); bot.setControlState(move, false); await new Promise(r => setTimeout(r, 100 + Math.random() * 300)); } await bot.look( bot.entity.yaw + ((Math.random() - 0.5) * 1.5), bot.entity.pitch + ((Math.random() - 0.5) * 0.3), true ); } catch (err) { logger.error(`[antiBotMove Hatası] Komut gönderilemedi: ${err && err.message ? err.message : err}`); } }; function scheduleNextCheck(delayMs) { if (periodicTimeoutHandle) { clearTimeout(periodicTimeoutHandle); } nextScheduledCheckAtMs = Date.now() + delayMs; nextScheduledCheckTimeStr = `${getFutureDateTimeString(delayMs)} (${formatDurationMs(delayMs)} sonra)`; logger.info(`[Planlayıcı] Bir sonraki besleyici kontrolü planlandı. Zaman: ${nextScheduledCheckTimeStr}`); periodicTimeoutHandle = setTimeout(async () => { if (!bot || !botIsReady) return; isMainCycleRunning = true; try { await goToIsland(); await new Promise(r => setTimeout(r, HOME_ARRIVAL_DELAY_MS)); await scanAndFeed(); } catch (err) { logger.error(`[Planlayıcı Hatası] Periyodik döngüde beklenmeyen hata: ${err && err.stack ? err.stack : err}`); try { scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } catch (e2) {} } finally { isMainCycleRunning = false; } }, delayMs); } function activateEntityAtNoLook(entity, position) { bot._client.write('use_entity', { target: entity.id, mouse: 2, sneaking: false, hand: 0, x: position.x - entity.position.x, y: position.y - entity.position.y, z: position.z - entity.position.z }); } async function scanAndFeedCore() { if (!bot || !botIsReady) return; if (!guiMutex.isLocked()) { logger.error("[GÜVENLİK İHLALİ] scanAndFeedCore kilitsiz çağrıldı! Çakışma riski var, işlem iptal ediliyor."); return; } if (Date.now() < (lastFullFeedAt + 30000)) { logger.warn("[Besleyici] Çok kısa süre önce işlem yapıldı. Güvenlik kilidi devrede."); return; } if (!bot.entities || !bot.entity || !bot.entity.position) { logger.warn("[Besleyici] Bot durumu (entities/position) henüz hazır değil, muhtemelen lag veya senkronizasyon gecikmesi. 10 dk sonra tekrar denenecek."); scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); 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; } const deep = deepSearchForString(e, TARGET_NAME_NORMS); if (deep.length > 0) { best = { entity: e, path: deep[0].path }; break; } } if (!best) { logger.info(`[${currentBotUsername}] /home sonrası tarama: besleyici minyon bulunamadı.`); bot.chat("/skyblock"); scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); return; } if (!best.entity.position) { logger.warn(`[${currentBotUsername}] Bulunan entity'nin pozisyon verisi yok (muhtemelen lag). 10 dk sonra tekrar denenecek.`); 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; } await new Promise(r => setTimeout(r, 400)); if (bot.currentWindow != null) { logger.warn(`[${currentBotUsername}] Beklenmedik şekilde eski bir pencere referansı hâlâ duruyor, kapatılıp temizleniyor...`); try { bot.closeWindow(bot.currentWindow); } catch (e) {} await new Promise(r => setTimeout(r, 300)); } const startTime = Date.now(); const endTime = startTime + WINDOW_OPEN_TIMEOUT_MS; let clickCount = 0; let isLoopActive = true; await new Promise((resolve) => { let clickInterval = null; const cleanFinish = () => { isLoopActive = false; clearInterval(clickInterval); windowOpenResolvers = []; resolve(); }; windowOpenResolvers.push(() => { cleanFinish(); }); const sendClickPacket = () => { if (!isLoopActive) return; if (!bot || !botIsReady) { logger.warn("[Tıklama Sistemi] Bot artık hazır değil (bağlantı koptu?). Döngü iptal ediliyor."); 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 { activateEntityAtNoLook(best.entity, best.entity.position); } catch (err) {} }; sendClickPacket(); clickInterval = setInterval(sendClickPacket, 600); }); } const FEED_LOCK_RETRY_DELAY_MS = 15 * 1000; const FEED_LOCK_MAX_RETRIES = 8; async function scanAndFeed(retryCount) { retryCount = retryCount || 0; if (!guiMutex.tryAcquire()) { if (retryCount < FEED_LOCK_MAX_RETRIES) { logger.warn(`[Besleyici] Zaten bir GUI işlemi sürüyor (kazıcı/çiftçi). ${formatDurationMs(FEED_LOCK_RETRY_DELAY_MS)} sonra tekrar denenecek (${retryCount + 1}/${FEED_LOCK_MAX_RETRIES}).`); setSafeTimeout(() => { scanAndFeed(retryCount + 1); }, FEED_LOCK_RETRY_DELAY_MS); } else { logger.error("[Besleyici] Kilit uzun süredir meşgul, besleyici turu atlanıyor. Döngünün ölmemesi için normal süre sonra tekrar planlanıyor."); try { scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } catch (e2) {} } return; } try { await scanAndFeedCore(); } catch (err) { logger.error(`[scanAndFeed Hatası] Beklenmeyen bir hata oluştu, döngü güvenle durduruldu: ${err && err.stack ? err.stack : err}`); try { scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } catch (e2) {} } finally { guiMutex.release(); } } async function scanAndFeedManual() { logger.info("[Besleyici - Manuel] Panelden istek geldi, sırasını bekliyor (öncelikli)..."); await guiMutex.acquire(); try { logger.info("[Besleyici - Manuel] Kilit alındı, kontrol başlıyor."); await scanAndFeedCore(); } catch (err) { logger.error(`[scanAndFeedManual Hatası] ${err && err.stack ? err.stack : err}`); } finally { guiMutex.release(); } } async function activateEntityAndAwaitWindow(entity, labelForLogs) { if (!bot || !botIsReady || !entity || !entity.position) { return { success: false, reason: "Bot veya entity hazır değil" }; } if (!bot.entity || !bot.entity.position) { return { success: false, reason: "Bot pozisyon verisi henüz hazır değil" }; } const dist = bot.entity.position.distanceTo(entity.position); if (dist > 7) { return { success: false, reason: `Çok uzak (${dist.toFixed(2)} blok, max 7)` }; } await new Promise(r => setTimeout(r, 400)); if (bot.currentWindow != null) { logger.warn(`[Tıklama Sistemi - ${labelForLogs}] Beklenmedik şekilde eski bir pencere referansı hâlâ duruyor, kapatılıp temizleniyor...`); try { bot.closeWindow(bot.currentWindow); } catch (e) {} await new Promise(r => setTimeout(r, 300)); } const startTime = Date.now(); const endTime = startTime + WINDOW_OPEN_TIMEOUT_MS; let clickCount = 0; let isLoopActive = true; let timedOut = false; await new Promise((resolve) => { let clickInterval = null; const cleanFinish = () => { isLoopActive = false; clearInterval(clickInterval); windowOpenResolvers = []; resolve(); }; windowOpenResolvers.push(() => { cleanFinish(); }); const sendClickPacket = () => { if (!isLoopActive) return; if (!bot || !botIsReady) { logger.warn(`[Tıklama Sistemi - ${labelForLogs}] Bot artık hazır değil. Döngü iptal ediliyor.`); cleanFinish(); return; } if (Date.now() > endTime) { logger.warn(`[Tıklama Sistemi - ${labelForLogs}] ${WINDOW_OPEN_TIMEOUT_MS} ms doldu ancak menü sunucudan gelmedi. Pes ediliyor.`); timedOut = true; cleanFinish(); return; } clickCount++; logger.info(`[Tıklama Sistemi - ${labelForLogs}] Menü tespiti başarısız. Deneme #${clickCount} gönderiliyor... (id:${entity.id})`); try { activateEntityAtNoLook(entity, entity.position); } catch (err) {} }; sendClickPacket(); clickInterval = setInterval(sendClickPacket, 600); }); if (timedOut) { return { success: false, reason: "Pencere zaman aşımına uğradı" }; } return { success: true }; } function findAllKaziciEntities() { const arr = Object.values(bot.entities); const matches = []; const seenIds = new Set(); for (const e of arr) { const name = getEntityReadableName(e); let matched = isKaziciName(name); if (!matched) { const deep = deepSearchForString(e, TARGET_KAZICI_NORMS); matched = deep.length > 0; } if (matched && e && e.id != null && !seenIds.has(e.id)) { seenIds.add(e.id); matches.push(e); } } return matches; } async function scanAndCollectMinersCore() { if (!bot || !botIsReady) return; if (!guiMutex.isLocked()) { logger.error("[GÜVENLİK İHLALİ] scanAndCollectMinersCore kilitsiz çağrıldı! Çakışma riski var, işlem iptal ediliyor."); return; } if (!bot.entities || !bot.entity || !bot.entity.position) { logger.warn("[Kazıcı Toplama] Bot durumu (entities/position) henüz hazır değil, muhtemelen lag. Bu tur atlanıyor."); return; } const miners = findAllKaziciEntities(); if (miners.length === 0) { logger.info(`[${currentBotUsername}] Kazıcı minyon bulunamadı, depo aktarım turu atlandı.`); return; } logger.info(`[${currentBotUsername}] ${miners.length} adet kazıcı minyon bulundu. Sırayla depo aktarımı yapılacak (aralarda ${formatDurationMs(KAZICI_BETWEEN_DELAY_MS)} bekleme).`); for (let i = 0; i < miners.length; i++) { if (!bot || !botIsReady) { logger.warn("[Kazıcı Toplama] Bot artık hazır değil, tur erken sonlandırılıyor."); break; } const entity = miners[i]; const label = `Kazıcı #${i + 1}/${miners.length}`; if (!entity || !entity.position) { logger.warn(`[Kazıcı Toplama] ${label} pozisyon verisi kayboldu (lag olabilir), atlanıyor.`); } else { logger.info(`[${currentBotUsername}] ${label} (id:${entity.id}) aktive ediliyor...`); const result = await activateEntityAndAwaitWindow(entity, label); if (!result.success) { logger.warn(`[Kazıcı Toplama] ${label} başarısız: ${result.reason}`); } else { logger.info(`[Kazıcı Toplama] ${label} depo aktarım işlemi tamamlandı.`); } } if (i < miners.length - 1) { await new Promise(r => setTimeout(r, KAZICI_BETWEEN_DELAY_MS)); } } logger.info(`[${currentBotUsername}] Kazıcı minyon depo aktarım turu tamamlandı.`); } async function scanAndCollectMiners() { if (!guiMutex.tryAcquire()) { logger.warn("[Kazıcı Toplama] Zaten bir GUI işlemi sürüyor (besleyici/kazıcı/çiftçi). Bu tur atlanıyor."); return; } try { await scanAndCollectMinersCore(); } catch (err) { logger.error(`[scanAndCollectMiners Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`); } finally { guiMutex.release(); } } async function scanAndCollectMinersManual() { logger.info("[Kazıcı Toplama - Manuel] Panelden istek geldi, sırasını bekliyor (öncelikli)..."); await guiMutex.acquire(); try { logger.info("[Kazıcı Toplama - Manuel] Kilit alındı, tur başlıyor."); await scanAndCollectMinersCore(); lastKaziciCheckTimeStr = getExtendedDateTimeString(); } catch (err) { logger.error(`[scanAndCollectMinersManual Hatası] ${err && err.stack ? err.stack : err}`); } finally { guiMutex.release(); } } async function openCiftciAndSellAllCore() { if (!bot || !botIsReady) return; if (!guiMutex.isLocked()) { logger.error("[GÜVENLİK İHLALİ] openCiftciAndSellAllCore kilitsiz çağrıldı! Çakışma riski var, işlem iptal ediliyor."); return; } ciftciWindowPurpose = "sell"; logger.info(`[${currentBotUsername}] Çiftçi (Hepsini Sat) menüsü açılıyor (${CIFTCI_COMMAND})...`); if (bot.currentWindow != null) { logger.warn(`[${currentBotUsername}] [Çiftçi] Beklenmedik şekilde eski bir pencere referansı hâlâ duruyor, kapatılıp temizleniyor...`); try { bot.closeWindow(bot.currentWindow); } catch (e) {} await new Promise(r => setTimeout(r, 300)); } let timedOut = false; await new Promise((resolve) => { let timeoutHandle = null; const cleanFinish = () => { clearTimeout(timeoutHandle); windowOpenResolvers = []; resolve(); }; windowOpenResolvers.push(() => { cleanFinish(); }); timeoutHandle = setTimeout(() => { logger.warn(`[Çiftçi] ${formatDurationMs(CIFTCI_WINDOW_TIMEOUT_MS)} doldu ancak menü sunucudan gelmedi. Pes ediliyor.`); timedOut = true; cleanFinish(); }, CIFTCI_WINDOW_TIMEOUT_MS); if (!bot || !botIsReady) { logger.warn("[Çiftçi] Bot artık hazır değil. İşlem iptal ediliyor."); timedOut = true; cleanFinish(); return; } logger.info(`[Çiftçi] Menü komutu gönderiliyor (tek seferlik)...`); try { bot.chat(CIFTCI_COMMAND); } catch (e) {} }); if (timedOut) { logger.warn("[Çiftçi] Menü açılamadı, bu tur atlanıyor."); } else { logger.info(`[${currentBotUsername}] Çiftçi menü turu tamamlandı.`); } } async function openCiftciAndSellAll() { if (!guiMutex.tryAcquire()) { logger.warn("[Çiftçi] Zaten bir GUI işlemi sürüyor (besleyici/kazıcı/çiftçi). Bu tur atlanıyor."); return; } try { await openCiftciAndSellAllCore(); } catch (err) { logger.error(`[openCiftciAndSellAll Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`); } finally { guiMutex.release(); } } async function openCiftciAndSellAllManual() { logger.info("[Çiftçi - Manuel] Panelden istek geldi, sırasını bekliyor (öncelikli)..."); await guiMutex.acquire(); try { logger.info("[Çiftçi - Manuel] Kilit alındı, satış başlıyor."); await openCiftciAndSellAllCore(); lastCiftciSellTimeStr = getExtendedDateTimeString(); } catch (err) { logger.error(`[openCiftciAndSellAllManual Hatası] ${err && err.stack ? err.stack : err}`); } finally { guiMutex.release(); } } function stripColorAndGetLoreLines(window) { if (!window || !Array.isArray(window.slots)) return null; for (const slot of window.slots) { if (!slot) continue; let nameTxt = ""; const customNameData = getComponentData(slot, "custom_name"); if (customNameData !== undefined) { nameTxt = flattenTextComponent(customNameData); } if (!nameTxt && slot.nbt) { const nameRaw = safeGet(slot.nbt, ["value", "display", "value", "Name", "value"]); try { const maybe = JSON.parse(nameRaw); if (maybe && maybe.extra) nameTxt = maybe.extra.map(x => x.text || "").join(""); else if (maybe && maybe.text) nameTxt = maybe.text; } catch (e) { nameTxt = nameRaw || ""; } } if (nameTxt.includes(CIFTCI_FILTER_ITEM_NAME_KEYWORD)) { return { slot, index: window.slots.indexOf(slot) }; } } return null; }; function getFilterItemLoreEntries(filterSlot) { const entries = []; const loreData = getComponentData(filterSlot, "lore"); if (Array.isArray(loreData)) { for (const line of loreData) { entries.push(parseComponentLoreLine(line)); } if (entries.length > 0) return entries; } try { const lore = safeGet(filterSlot.nbt, ["value", "display", "value", "Lore", "value"]); if (!Array.isArray(lore)) return entries; for (const line of lore) { const raw = line.value || line; let plain = ""; let color = null; try { const maybe = JSON.parse(raw); if (maybe && Array.isArray(maybe.extra)) { for (const part of maybe.extra) { if (part.text) plain += part.text; if (part.color && !color) color = part.color; } } else if (maybe && maybe.text) { plain = maybe.text; } } catch (e) { plain = raw; } entries.push({ text: plain.trim(), color }); } } catch (e) {} return entries; }; function findCurrentFilterIndex(loreEntries) { for (let i = 0; i < CIFTCI_FILTER_MODES.length; i++) { const modeName = CIFTCI_FILTER_MODES[i]; const match = loreEntries.find(e => e.text.includes(modeName) && (e.text.includes("»") || e.color === CIFTCI_ACTIVE_FILTER_COLOR) ); if (match) return i; } const colorMatch = loreEntries.find(e => e.color === CIFTCI_ACTIVE_FILTER_COLOR); if (colorMatch) { for (let i = 0; i < CIFTCI_FILTER_MODES.length; i++) { if (colorMatch.text.includes(CIFTCI_FILTER_MODES[i])) return i; } } return -1; } async function waitForFilterItem(maxRetries) { for (let attempt = 0; attempt <= maxRetries; attempt++) { await new Promise(r => setTimeout(r, attempt === 0 ? CIFTCI_FILTER_STEP_WAIT_MS : CIFTCI_FILTER_RETRY_WAIT_MS)); if (!bot || !bot.currentWindow) return null; const found = stripColorAndGetLoreLines(bot.currentWindow); if (found) return found; } return null; } function extractStorageItemInfo(window, internalName) { if (!window || !Array.isArray(window.slots)) return null; const slot = window.slots.find(s => s && s.name === internalName); if (!slot) return null; const texts = getItemDisplayTexts(slot); const joined = texts.join(" | "); function grab(label) { const re = new RegExp(label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*:?\\s*([\\d.,%~]+)", "i"); const m = joined.match(re); return m ? m[1] : null; } return { depo: grab("Depo"), limit: grab("Limit"), dolulukOrani: grab("Doluluk Oranı"), birimFiyati: grab("Birim Fiyatı"), anlikNetKazanc: grab("Anlık Net Kazanç"), dakikalikUretim: grab("Dakikalık üretim"), saatlikUretim: grab("Saatlik üretim"), gunlukUretim: grab("Günlük üretim") }; } function findSlotByInternalName(window, internalName) { if (!window || !Array.isArray(window.slots)) return -1; for (let i = 0; i < window.slots.length; i++) { const s = window.slots[i]; if (s && s.name === internalName) return i; } return -1; } async function navigateCiftciFilterToGenerator() { if (!bot || !bot.currentWindow) return false; let attempts = 0; while (attempts < CIFTCI_FILTER_MAX_RETRIES) { attempts++; const foundItem = await waitForFilterItem(CIFTCI_FILTER_MAX_RETRIES); if (!foundItem) { logger.warn("[Çiftçi Filtre] Filtre item'ı bulunamadı, deneme durduruldu."); return false; } const loreEntries = getFilterItemLoreEntries(foundItem.slot); const currentIndex = findCurrentFilterIndex(loreEntries); if (currentIndex === -1) { logger.warn("[Çiftçi Filtre] Mevcut filtre modu tespit edilemedi."); return false; } if (currentIndex === CIFTCI_TARGET_FILTER_INDEX) { logger.info("[Çiftçi Filtre] Jeneratör materyalleri moduna ulaşıldı."); return true; } const total = CIFTCI_FILTER_MODES.length; const forwardDist = (CIFTCI_TARGET_FILTER_INDEX - currentIndex + total) % total; const backwardDist = (currentIndex - CIFTCI_TARGET_FILTER_INDEX + total) % total; const useLeftClick = forwardDist <= backwardDist; const mouseButton = useLeftClick ? 0 : 1; logger.info(`[Çiftçi Filtre] Mevcut: "${CIFTCI_FILTER_MODES[currentIndex]}" -> Hedef: "${CIFTCI_FILTER_MODES[CIFTCI_TARGET_FILTER_INDEX]}". ${useLeftClick ? "Sol tık (aşağı)" : "Sağ tık (yukarı)"} kullanılacak.`); try { await bot.clickWindow(foundItem.index, mouseButton, 0); } catch (e) { logger.error(`[Çiftçi Filtre] Tıklama hatası: ${e && e.message ? e.message : e}`); return false; } } logger.warn("[Çiftçi Filtre] Azami deneme sayısına ulaşıldı, Jeneratör materyallerine geçilemedi."); return false; } async function scanStorageInfoCore() { if (!bot || !botIsReady) return; if (!guiMutex.isLocked()) { logger.error("[GÜVENLİK İHLALİ] scanStorageInfoCore kilitsiz çağrıldı! İşlem iptal ediliyor."); return; } ciftciWindowPurpose = "scan"; logger.info(`[${currentBotUsername}] Storage bilgisi taraması başlıyor: çiftçi menüsü (${CIFTCI_COMMAND}) açılıyor...`); if (bot.currentWindow != null) { try { bot.closeWindow(bot.currentWindow); } catch (e) {} await new Promise(r => setTimeout(r, 300)); } let windowOpened = false; await new Promise((resolve) => { let timeoutHandle = null; const cleanFinish = () => { clearTimeout(timeoutHandle); windowOpenResolvers = []; resolve(); }; windowOpenResolvers.push(() => { windowOpened = true; cleanFinish(); }); timeoutHandle = setTimeout(() => { logger.warn("[Storage Tarama] Menü açılamadı (zaman aşımı)."); cleanFinish(); }, CIFTCI_WINDOW_TIMEOUT_MS); try { bot.chat(CIFTCI_COMMAND); } catch (e) {} }); if (!windowOpened || !bot.currentWindow) { logger.warn("[Storage Tarama] Menü açılmadığı için tarama atlandı."); ciftciWindowPurpose = "sell"; return; } const reached = await navigateCiftciFilterToGenerator(); if (reached && bot.currentWindow) { const newCache = { diamond: extractStorageItemInfo(bot.currentWindow, "diamond"), emerald: extractStorageItemInfo(bot.currentWindow, "emerald"), iron_block: extractStorageItemInfo(bot.currentWindow, "iron_block"), lastUpdated: getExtendedDateTimeString() }; storageInfoCache = newCache; logger.info(`[Storage Tarama] Bilgiler güncellendi: ${JSON.stringify(newCache)}`); } else { logger.warn("[Storage Tarama] Jeneratör materyallerine ulaşılamadığı için veri güncellenmedi."); } try { if (bot.currentWindow) bot.closeWindow(bot.currentWindow); } catch (e) {} ciftciWindowPurpose = "sell"; } async function scanStorageInfo() { if (!guiMutex.tryAcquire()) { logger.info("[Storage Tarama] Başka bir GUI işlemi sürüyor, bu tur atlanıyor."); return; } try { await scanStorageInfoCore(); } catch (err) { logger.error(`[scanStorageInfo Hatası] ${err && err.stack ? err.stack : err}`); ciftciWindowPurpose = "sell"; } finally { guiMutex.release(); } }; async function scanStorageInfoManual() { logger.info("[Storage Tarama - Manuel] Panelden istek geldi, sırasını bekliyor (öncelikli)..."); await guiMutex.acquire(); try { logger.info("[Storage Tarama - Manuel] Kilit alındı, tarama başlıyor."); await scanStorageInfoCore(); } catch (err) { logger.error(`[scanStorageInfoManual Hatası] ${err && err.stack ? err.stack : err}`); ciftciWindowPurpose = "sell"; } finally { guiMutex.release(); } }; // ===================================================================== // SİPARİŞ (ORDER) SİSTEMİ // ===================================================================== async function waitForNextWindowOpen(timeoutMs) { return new Promise((resolve) => { let timeoutHandle = null; const cleanFinish = (opened) => { clearTimeout(timeoutHandle); windowOpenResolvers = []; resolve(opened); }; windowOpenResolvers.push(() => cleanFinish(true)); timeoutHandle = setTimeout(() => { cleanFinish(false); }, timeoutMs); }); } function findSlotContainingNick(window, nickNorm, itemInternalName) { if (!window || !Array.isArray(window.slots)) return -1; for (let i = 0; i < window.slots.length; i++) { const slot = window.slots[i]; if (!slot) continue; // ÖNEMLİ: hem item tipi hem de nick eşleşmeli. // itemInternalName verilmişse (örn. "diamond"), slotun gerçek // Minecraft item id'si ile karşılaştırıyoruz (dilden bağımsız, kesin). if (itemInternalName && slot.name !== itemInternalName) continue; const texts = getItemDisplayTexts(slot); for (const t of texts) { if (matchesAnyName(t, [nickNorm])) return i; } } return -1; } async function processOrderCore(itemInternalName, nick) { if (!bot || !botIsReady) return { success: false, message: "Bot hazır değil." }; if (!guiMutex.isLocked()) { logger.error("[GÜVENLİK İHLALİ] processOrderCore kilitsiz çağrıldı! İşlem iptal ediliyor."); return { success: false, message: "Kilitsiz çağrı engellendi." }; } if (!ORDER_ALLOWED_ITEMS.includes(itemInternalName)) { return { success: false, message: `Geçersiz item: ${itemInternalName}. İzin verilenler: ${ORDER_ALLOWED_ITEMS.join(", ")}` }; } if (!nick || typeof nick !== "string" || nick.trim().length === 0) { return { success: false, message: "nick parametresi zorunlu." }; } // --- 1) Çiftçi menüsünü aç, jeneratör materyalleri filtresine geç --- ciftciWindowPurpose = "order"; logger.info(`[Sipariş] Çiftçi menüsü açılıyor (${CIFTCI_COMMAND})...`); if (bot.currentWindow != null) { try { bot.closeWindow(bot.currentWindow); } catch (e) {} await new Promise(r => setTimeout(r, 300)); } let windowOpened = false; await new Promise((resolve) => { let timeoutHandle = null; const cleanFinish = () => { clearTimeout(timeoutHandle); windowOpenResolvers = []; resolve(); }; windowOpenResolvers.push(() => { windowOpened = true; cleanFinish(); }); timeoutHandle = setTimeout(() => { logger.warn("[Sipariş] Çiftçi menüsü açılamadı (zaman aşımı)."); cleanFinish(); }, CIFTCI_WINDOW_TIMEOUT_MS); try { bot.chat(CIFTCI_COMMAND); } catch (e) {} }); if (!windowOpened || !bot.currentWindow) { ciftciWindowPurpose = "sell"; return { success: false, message: "Çiftçi menüsü açılamadı." }; } const reached = await navigateCiftciFilterToGenerator(); if (!reached || !bot.currentWindow) { try { if (bot.currentWindow) bot.closeWindow(bot.currentWindow); } catch (e) {} ciftciWindowPurpose = "sell"; return { success: false, message: "Jeneratör materyalleri filtresine ulaşılamadı." }; } // --- 2) İstenen item'ın slotuna shift+sağ tık (envanter dolana kadar verir) --- const itemSlotIndex = findSlotByInternalName(bot.currentWindow, itemInternalName); if (itemSlotIndex === -1) { try { bot.closeWindow(bot.currentWindow); } catch (e) {} ciftciWindowPurpose = "sell"; return { success: false, message: `'${itemInternalName}' slotu jeneratör materyalleri filtresinde bulunamadı.` }; } try { logger.info(`[Sipariş] '${itemInternalName}' slotuna (idx:${itemSlotIndex}) shift+sağ tık gönderiliyor...`); await bot.clickWindow(itemSlotIndex, 1, 1); // mouseButton=1 (sağ), mode=1 (shift) await new Promise(r => setTimeout(r, KAZICI_ACTION_SETTLE_DELAY_MS)); } catch (e) { logger.error(`[Sipariş] Shift+sağ tık hatası: ${e && e.message ? e.message : e}`); } // --- YENİ: Çiftçiden gerçekten item alınmış mı doğrula --- // clickWindow hata fırlatmasa bile sunucu isteği görmezden gelmiş olabilir. // Envanterde ilgili item yoksa devam etmenin anlamı yok (Teslim Etme'ye zaten // aktarılacak bir şey olmaz), bu yüzden burada erkenden kontrol edip // anlamlı bir hata dönüyoruz. const itemsAfterWithdraw = bot.inventory.items().filter(it => it && it.name === itemInternalName); const withdrawnCount = itemsAfterWithdraw.reduce((sum, it) => sum + (it.count || 0), 0); logger.info(`[Sipariş] Çiftçiden çekildikten sonra envanterde '${itemInternalName}': ${withdrawnCount} adet.`); if (withdrawnCount === 0) { logger.warn(`[Sipariş] Çiftçi menüsünden '${itemInternalName}' envantere aktarılamadı (0 adet). İşlem iptal ediliyor.`); try { if (bot.currentWindow) bot.closeWindow(bot.currentWindow); } catch (e) {} ciftciWindowPurpose = "sell"; return { success: false, message: `Çiftçiden '${itemInternalName}' alınamadı (envanterde 0 adet). Depoda yeterli stok olmayabilir ya da tıklama sunucu tarafından işlenmedi.` }; } // --- 3) Menüyü kapat --- try { if (bot.currentWindow) bot.closeWindow(bot.currentWindow); } catch (e) {} // ÖNEMLİ: purpose burada "sell"e ÇEVRİLMEZ! /order akışı (Siparişler + // Teslim Etme pencereleri) henüz açılmadı. purpose "order" kaldığı sürece // windowOpen handler bu pencereleri otomatik kapatmayacak. await new Promise(r => setTimeout(r, 300)); // --- 4) antiBotMove -> /is go -> 5sn -> /is go --- try { await antiBotMove(); } catch (e) {} try { bot.chat(GO_ISLAND_COMMAND); } catch (e) {} await new Promise(r => setTimeout(r, ORDER_IS_GO_SECOND_DELAY_MS)); try { bot.chat(GO_ISLAND_COMMAND); } catch (e) {} await new Promise(r => setTimeout(r, HOME_ARRIVAL_DELAY_MS)); // --- 5) /order gönder --- logger.info(`[Sipariş] ${ORDER_COMMAND_PREFIX} ${nick} komutu gönderiliyor...`); let orderWindowOpened = false; await new Promise((resolve) => { let timeoutHandle = null; const cleanFinish = () => { clearTimeout(timeoutHandle); windowOpenResolvers = []; resolve(); }; windowOpenResolvers.push(() => { orderWindowOpened = true; cleanFinish(); }); timeoutHandle = setTimeout(() => { logger.warn("[Sipariş] /order penceresi açılamadı (zaman aşımı)."); cleanFinish(); }, ORDER_WINDOW_TIMEOUT_MS); try { bot.chat(`${ORDER_COMMAND_PREFIX} ${nick}`); } catch (e) {} }); if (!orderWindowOpened || !bot.currentWindow) { ciftciWindowPurpose = "sell"; return { success: false, message: "/order penceresi açılmadı." }; } // --- 6) +500ms bekle, güncel pencereyi oku (windowOpen anındaki değil) --- await new Promise(r => setTimeout(r, ORDER_WINDOW_SETTLE_DELAY_MS)); if (!bot.currentWindow) { ciftciWindowPurpose = "sell"; return { success: false, message: "Pencere beklenmedik şekilde kapandı." }; } // --- 7) Lore'da nick geçen slotu bul, sol tık ile seç --- const nickVariants = normalizeVariants(nick); let targetSlotIndex = -1; // 1. ÖNCELİK: hem item tipi hem nick birlikte eşleşsin for (const nv of nickVariants) { targetSlotIndex = findSlotContainingNick(bot.currentWindow, nv, itemInternalName); if (targetSlotIndex !== -1) break; } if (targetSlotIndex === -1) { logger.warn(`[Sipariş] '${nick}' + '${itemInternalName}' eşleşen item bulunamadı, pencere kapatılıyor.`); try { bot.closeWindow(bot.currentWindow); } catch (e) {} ciftciWindowPurpose = "sell"; return { success: false, message: `'${nick}' kullanıcısının '${itemInternalName}' siparişi bulunamadı (belki farklı item için sipariş vermiş, ya da hiç sipariş yok).` }; } logger.info(`[Sipariş] '${nick}' içeren item bulundu (idx:${targetSlotIndex}), sol tık gönderiliyor...`); // --- YENİ: nick'e tıkladıktan sonra sabit 500ms yerine GERÇEK yeni // pencere açılışını (Teslim Etme) bekliyoruz. Bu, mevcut buğun ana // sebebiydi: sunucu 500ms'den geç yanıt verirse eski (Siparişler) // penceresine göre hesaplanmış slotlara tıklanıyor ve hiçbir şey // taşınmıyordu (sunucu paketi reddetmediği için hata da görünmüyordu). const windowBeforeSelect = bot.currentWindow; try { await bot.clickWindow(targetSlotIndex, 0, 0); } catch (e) { logger.error(`[Sipariş] Slot seçim tıklaması hatası: ${e && e.message ? e.message : e}`); } // --- 8) Teslim Etme penceresinin gerçekten açılmasını bekle --- const deliveryOpened = await waitForNextWindowOpen(ORDER_DELIVERY_WINDOW_WAIT_MS); if (!deliveryOpened || !bot.currentWindow) { logger.warn("[Sipariş] Teslim Etme penceresi zaman aşımına uğradı ya da açılmadı, işlem yarım kalmış olabilir."); ciftciWindowPurpose = "sell"; return { success: false, message: "Teslim Etme penceresi açılmadı (zaman aşımı)." }; } // Pencere objesinin gerçekten DEĞİŞTİĞİNİ doğrula (bazı sunucular aynı id'yi // update edip windowOpen yerine sadece slot update gönderebilir; bu durumda // en azından title kontrolüyle devam ediyoruz). if (bot.currentWindow === windowBeforeSelect) { logger.warn("[Sipariş] windowOpen tetiklendi ama pencere referansı değişmedi, dikkatli devam ediliyor."); } const currentTitle = parseWindowTitleToString(bot.currentWindow.title); const looksLikeDelivery = matchesAnyName(currentTitle, normalizeVariants(ORDER_DELIVERY_TITLE_KEYWORD)); logger.info(`[Sipariş] Yeni pencere açıldı: "${currentTitle}" (Teslim Etme eşleşmesi: ${looksLikeDelivery}). Envanterdeki '${itemInternalName}' item'ları aktarılıyor...`); if (!looksLikeDelivery) { logger.warn(`[Sipariş] Açılan pencere Teslim Etme'ye benzemiyor, yine de mevcut pencereye aktarım denenecek: "${currentTitle}"`); } // Ek güvenlik: envanterde gerçekten aktarılacak item var mı, tekrar kontrol et // (nick seçme / /is go arasında item'lar herhangi bir sebeple kaybolmuş olabilir). const invStartCheck = bot.currentWindow.inventoryStart; const invCheckBeforeTransfer = bot.currentWindow.slots .slice(invStartCheck) .filter(it => it && it.name === itemInternalName); if (invCheckBeforeTransfer.length === 0) { logger.warn(`[Sipariş] Aktarım öncesi envanterde '${itemInternalName}' bulunamadı, aktarım atlanıyor.`); try { if (bot.currentWindow) bot.closeWindow(bot.currentWindow); } catch (e) {} ciftciWindowPurpose = "sell"; return { success: false, message: `Envanterde '${itemInternalName}' kalmamış, Teslim Etme'ye aktarılacak item yok.` }; } const windowAtTransferStart = bot.currentWindow; let transferredCount = 0; let keepGoing = true; let safetyCounter = 0; let stuckSlot = null; let stuckSlotRepeatCount = 0; while (keepGoing && safetyCounter < 40) { safetyCounter++; // Pencere döngü sırasında değişti/kapandıysa (ör. sunucu Teslim Etme'yi // kapattıysa) devam etmenin bir anlamı yok, güvenle çık. if (!bot.currentWindow || bot.currentWindow !== windowAtTransferStart) { logger.warn("[Sipariş] Aktarım sırasında pencere değişti/kapandı, döngü durduruluyor."); break; } // ÖNEMLİ FIX: bot.inventory.items() custom pencere açıkken YANLIŞ slot // numarası döndürebiliyor (inventoryStart offsetini hesaba katmıyor). // Bunun yerine doğrudan currentWindow.slots üzerinden, gerçek (offsetli) // slot index'ini buluyoruz. const invStart = bot.currentWindow.inventoryStart; const winSlots = bot.currentWindow.slots; let realSlotIndex = -1; for (let i = invStart; i < winSlots.length; i++) { if (winSlots[i] && winSlots[i].name === itemInternalName) { realSlotIndex = i; break; } } if (realSlotIndex === -1) { keepGoing = false; break; } if (stuckSlot === realSlotIndex) { stuckSlotRepeatCount++; if (stuckSlotRepeatCount >= 3) { logger.warn(`[Sipariş] Slot ${realSlotIndex} 3 kez üst üste taşınamadı (Teslim Etme dolu/limitli olabilir), aktarım durduruluyor.`); keepGoing = false; break; } } else { stuckSlot = realSlotIndex; stuckSlotRepeatCount = 0; } try { await bot.clickWindow(realSlotIndex, 0, 1); // mouseButton=0 (sol), mode=1 (shift) await new Promise(r => setTimeout(r, 1000 + Math.floor(Math.random() * 200))); const stillThere = bot.currentWindow.slots[realSlotIndex]; if (!stillThere || stillThere.name !== itemInternalName) { transferredCount++; } else { logger.warn(`[Sipariş] Slot ${realSlotIndex} tıklandı ama item hâlâ envanterde, taşınmamış olabilir.`); } } catch (e) { logger.error(`[Sipariş] Envanterden aktarım tıklaması hatası: ${e && e.message ? e.message : e}`); keepGoing = false; } } if (safetyCounter >= 40) { logger.warn(`[Sipariş] Güvenlik limitine (40 deneme) ulaşıldı. Teslim Etme kutusu dolu/limitli olabilir, transfer yarım kalmış olabilir.`); } // --- 9) Pencereyi önce kapat, sonra sayım yap (pencere açıkken bot.inventory.items() // yanlış slot offseti hesaplayabildiği için, güvenilir sayım için kapanmasını bekliyoruz) --- try { if (bot.currentWindow) bot.closeWindow(bot.currentWindow); } catch (e) {} ciftciWindowPurpose = "sell"; // akış tamamen bitti, artık normal davranışa dön await new Promise(r => setTimeout(r, 300)); // pencere kapanışının client tarafında senkronize olmasını bekle const remainingAfterTransfer = bot.inventory.items() .filter(it => it && it.name === itemInternalName) .reduce((sum, it) => sum + (it.count || 0), 0); const stoppedReason = safetyCounter >= 40 ? "güvenlik limiti (40 deneme)" : (remainingAfterTransfer > 0 ? "Teslim Etme dolu/limitli olabilir" : "envanter boşaldı"); logger.info( `[Sipariş] Envanterden Teslim Etme'ye aktarım tamamlandı: ` + `${transferredCount} yığın (stack) gönderildi, ` + `envanterde '${itemInternalName}' kaldı: ${remainingAfterTransfer} adet ` + `(durma sebebi: ${stoppedReason}).` ); return { success: transferredCount > 0, message: transferredCount > 0 ? "Sipariş işlemi tamamlandı." : "Hiçbir yığın Teslim Etme'ye aktarılamadı.", item: itemInternalName, nick, withdrawn_from_ciftci: withdrawnCount, inventory_transferred_batches: transferredCount, remaining_in_inventory: remainingAfterTransfer }; } async function processOrderManual(itemInternalName, nick) { logger.info(`[Sipariş - Manuel] Panelden istek geldi (item:${itemInternalName}, nick:${nick}), sırasını bekliyor (öncelikli)...`); await guiMutex.acquire(); try { logger.info("[Sipariş - Manuel] Kilit alındı, işlem başlıyor."); const result = await processOrderCore(itemInternalName, nick); lastOrderTimeStr = `${getExtendedDateTimeString()} (item:${itemInternalName}, nick:${nick}, sonuç:${result.success ? "başarılı" : "başarısız"})`; return result; } catch (err) { logger.error(`[processOrderManual Hatası] ${err && err.stack ? err.stack : err}`); return { success: false, message: "Beklenmeyen bir hata oluştu." }; } finally { guiMutex.release(); } } function startStorageScanPeriodic() { if (storageScanIntervalHandle) return; storageScanIntervalHandle = setInterval(() => { if (!bot || !botIsReady) return; if (manualStopRequested || isDestroying) return; scanStorageInfo(); }, STORAGE_SCAN_INTERVAL_MS); logger.info(`[Storage Tarama] Bağımsız döngü başlatıldı (her ${formatDurationMs(STORAGE_SCAN_INTERVAL_MS)}).`); setSafeTimeout(() => { scanStorageInfo(); }, 30 * 1000); } function startKaziciPeriodic() { if (kaziciIntervalHandle) return; const runCycle = async () => { if (!bot || !botIsReady) return; if (manualStopRequested || isDestroying) return; if (!guiMutex.tryAcquire()) { logger.info("[Kazıcı Periyodik] Başka bir GUI işlemi sürüyor, bu tur atlanıyor."); return; } try { logger.info("[Kazıcı Periyodik] Tur başlıyor: kazıcı depo aktarımı."); await scanAndCollectMinersCore(); lastKaziciCheckTimeStr = getExtendedDateTimeString(); logger.info(`[Kazıcı Periyodik] Tur tamamlandı. ${formatDurationMs(KAZICI_CYCLE_INTERVAL_MS)} sonra tekrar denenecek.`); } catch (err) { logger.error(`[Kazıcı Periyodik Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`); } finally { guiMutex.release(); nextKaziciAtMs = Date.now() + KAZICI_CYCLE_INTERVAL_MS; nextKaziciTimeStr = `${getFutureDateTimeString(KAZICI_CYCLE_INTERVAL_MS)} (${formatDurationMs(KAZICI_CYCLE_INTERVAL_MS)} sonra)`; } }; kaziciIntervalHandle = setInterval(runCycle, KAZICI_CYCLE_INTERVAL_MS); nextKaziciAtMs = Date.now() + KAZICI_CYCLE_INTERVAL_MS; nextKaziciTimeStr = `${getFutureDateTimeString(KAZICI_CYCLE_INTERVAL_MS)} (${formatDurationMs(KAZICI_CYCLE_INTERVAL_MS)} sonra)`; logger.info(`[Kazıcı Periyodik] Bağımsız döngü başlatıldı (her ${formatDurationMs(KAZICI_CYCLE_INTERVAL_MS)}).`); } function startCiftciPeriodic() { if (ciftciIntervalHandle) return; const runCycle = async () => { if (!bot || !botIsReady) return; if (manualStopRequested || isDestroying) return; if (!guiMutex.tryAcquire()) { logger.info("[Çiftçi Periyodik] Başka bir GUI işlemi sürüyor, bu tur atlanıyor."); return; } try { logger.info("[Çiftçi Periyodik] Tur başlıyor: çiftçi hepsini-sat."); await openCiftciAndSellAllCore(); lastCiftciSellTimeStr = getExtendedDateTimeString(); logger.info(`[Çiftçi Periyodik] Tur tamamlandı. ${formatDurationMs(CIFTCI_CYCLE_INTERVAL_MS)} sonra tekrar denenecek.`); } catch (err) { logger.error(`[Çiftçi Periyodik Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`); } finally { guiMutex.release(); nextCiftciAtMs = Date.now() + CIFTCI_CYCLE_INTERVAL_MS; nextCiftciTimeStr = `${getFutureDateTimeString(CIFTCI_CYCLE_INTERVAL_MS)} (${formatDurationMs(CIFTCI_CYCLE_INTERVAL_MS)} sonra)`; } }; ciftciIntervalHandle = setInterval(runCycle, CIFTCI_CYCLE_INTERVAL_MS); nextCiftciAtMs = Date.now() + CIFTCI_CYCLE_INTERVAL_MS; nextCiftciTimeStr = `${getFutureDateTimeString(CIFTCI_CYCLE_INTERVAL_MS)} (${formatDurationMs(CIFTCI_CYCLE_INTERVAL_MS)} sonra)`; logger.info(`[Çiftçi Periyodik] Bağımsız döngü başlatıldı (her ${formatDurationMs(CIFTCI_CYCLE_INTERVAL_MS)}).`); } bot.once("spawn", async () => { isConnecting = false; if (!bot) return; try { logger.info("Bot sunucuya giriş yaptı."); botIsReady = true; botUptimeStart = Date.now(); botActions.scanAndFeedManual = scanAndFeedManual; botActions.scanAndCollectMinersManual = scanAndCollectMinersManual; botActions.openCiftciAndSellAllManual = openCiftciAndSellAllManual; botActions.goToIslandManual = goToIsland; botActions.sendSkyblockManual = sendSkyblockCommand; botActions.antiBotMove = antiBotMove; botActions.sendMoneyToNick = sendMoneyToNick; botActions.scanStorageInfoManual = scanStorageInfoManual; botActions.processOrderManual = processOrderManual; startKaziciPeriodic(); startCiftciPeriodic(); startStorageScanPeriodic(); startStallWatcher(); if (config.utils && config.utils["auto-auth"] && config.utils["auto-auth"].enabled) { const password = config.utils["auto-auth"].password; setSafeTimeout(() => { try { if (bot && botIsReady) { bot.chat(`/login ${password}`); logger.info(`[${currentBotUsername}] Login komutu kullanıldı.`); } } catch (err) { logger.error(`[Spawn Hatası] /login komutu gönderilemedi: ${err && err.message ? err.message : err}`); } }, 2500); setSafeTimeout(() => { try { if (bot && botIsReady) { bot.chat(`/skyblock`); logger.info(`[${currentBotUsername}] Skyblock'a ışınlanma komutu kullanıldı.`); } } catch (err) { logger.error(`[Spawn Hatası] /skyblock komutu gönderilemedi: ${err && err.message ? err.message : err}`); } }, 7500); setSafeTimeout(async () => { if (!bot || !botIsReady) return; isMainCycleRunning = true; try { await goToIsland(); await new Promise(r => setTimeout(r, HOME_ARRIVAL_DELAY_MS)); await scanAndFeed(); } catch (err) { logger.error(`[Spawn Hatası] İlk kontrol döngüsünde beklenmeyen hata: ${err && err.stack ? err.stack : err}`); try { scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } catch (e2) {} } finally { isMainCycleRunning = false; } }, 12500); } } catch (err) { logger.error(`[Spawn Hatası] Spawn işleyicisinde beklenmeyen hata: ${err && err.stack ? err.stack : err}`); } }); bot.on("entityMoved", (entity) => { try { trackKaziciPoseIfWatched(entity); } catch (e) {} }); bot.on("entitySpawn", (entity) => { try { trackKaziciPoseIfWatched(entity); } catch (e) {} }); bot.on("windowOpen", async (window) => { if (!bot) return; try { const titleRaw = parseWindowTitleToString(window.title); const isBesleyiciWindow = matchesAnyName(titleRaw, TARGET_NAME_NORMS); const isKaziciWindow = !isBesleyiciWindow && matchesAnyName(titleRaw, TARGET_KAZICI_NORMS); const isCiftciWindow = !isBesleyiciWindow && !isKaziciWindow && (matchesAnyName(titleRaw, TARGET_CIFTCI_NORMS) || windowMatchesLoreKeyword(window, CIFTCI_LORE_FALLBACK_KEYWORDS)); if (!isBesleyiciWindow && !isKaziciWindow && !isCiftciWindow) { if (ciftciWindowPurpose === "order") { logger.info(`[${currentBotUsername}] Sipariş (/order) penceresi açıldı: ${titleRaw}`); } else { logger.info(`[${currentBotUsername}] Bi chest açıldı ama window title ı uyuşmadığı için kapatıldı. ${titleRaw}`); try { bot.closeWindow(window); } catch(e){} return; } } else if (isKaziciWindow) { try { logger.info(`[${currentBotUsername}] Kazıcı minyon paneli açıldı. Depo aktarım slotuna (oyun içi 18. slot / mineflayer idx ${KAZICI_STORAGE_TRANSFER_SLOT}) tıklanıyor...`); await bot.clickWindow(KAZICI_STORAGE_TRANSFER_SLOT, 0, 0); logger.info(`[${currentBotUsername}] Kazıcı depo aktarım tıklaması gönderildi.`); await new Promise(r => setTimeout(r, KAZICI_ACTION_SETTLE_DELAY_MS)); } catch (e) { logger.error(`[Kazıcı Arayüz Hatası] Depo aktarım tıklaması sunucu tarafından onaylanmadı: ${e && e.message ? e.message : e}`); } try { bot.closeWindow(window); } catch(e){} } else if (isCiftciWindow) { if (ciftciWindowPurpose === "scan" || ciftciWindowPurpose === "order") { // Storage tarama / sipariş modu: burada otomatik tıklama/kapatma yapma, // ilgili core fonksiyon kendi akışını yönetiyor. Sadece resolver'ı tetikle. } else { try { logger.info(`[${currentBotUsername}] Çiftçi (Hepsini Sat) menüsü açıldı. ${formatDurationMs(CIFTCI_CLICK_DELAY_AFTER_OPEN_MS)} bekleniyor, sonra slot ${CIFTCI_SELL_ALL_SLOT}'e ${CIFTCI_CLICK_REPEAT_COUNT} kez tıklanacak...`); await new Promise(r => setTimeout(r, CIFTCI_CLICK_DELAY_AFTER_OPEN_MS)); for (let i = 0; i < CIFTCI_CLICK_REPEAT_COUNT; i++) { if (!bot || !botIsReady) break; try { await bot.clickWindow(CIFTCI_SELL_ALL_SLOT, 0, 0); logger.info(`[${currentBotUsername}] Çiftçi hepsini-sat tıklaması gönderildi (${i + 1}/${CIFTCI_CLICK_REPEAT_COUNT}).`); } catch (clickErr) { logger.error(`[Çiftçi Arayüz Hatası] Tıklama #${i + 1} sunucu tarafından onaylanmadı: ${clickErr && clickErr.message ? clickErr.message : clickErr}`); } if (i < CIFTCI_CLICK_REPEAT_COUNT - 1) { await new Promise(r => setTimeout(r, CIFTCI_CLICK_REPEAT_GAP_MS)); } } await new Promise(r => setTimeout(r, KAZICI_ACTION_SETTLE_DELAY_MS)); } catch (e) { logger.error(`[Çiftçi Arayüz Hatası] Hepsini-sat tıklama turu sırasında beklenmeyen hata: ${e && e.message ? e.message : e}`); } try { bot.closeWindow(window); } catch(e){} } } else { 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})`; lastCheckAtMs = Date.now(); if (can <= 2) { 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 ${formatDurationMs(FULL_FEED_COOLDOWN_MS)} 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 { let calculatedMinutes = (can - 2) * 15; calculatedMinutes = Math.min(65, Math.max(15, calculatedMinutes)); const dynamicDelayMs = calculatedMinutes * 60 * 1000; logger.info(`[${currentBotUsername}] Minyon canı ${can} olarak okundu. Canın 2'ye düşmesi için ${formatDurationMs(dynamicDelayMs)} beklenecek.`); if (can >= 10) { lastFullFeedTimeStr = `${currentStamp} (Canı ${can} olduğu için besleme gerekmedi)`; } scheduleNextCheck(dynamicDelayMs); } } else { logger.warn(`[${currentBotUsername}] Menü açıldı ama can okunamadı. ${formatDurationMs(DEFAULT_CHECK_INTERVAL_MS)} sonra tekrar denenecek.`); scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } try { bot.closeWindow(window); } catch(e){} } } catch (err) { logger.error(`[windowOpen Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`); try { bot.closeWindow(window); } catch(e){} try { scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } catch (e2) {} } while (windowOpenResolvers.length > 0) { const fn = windowOpenResolvers.shift(); if (typeof fn === 'function') { try { fn(); } catch (e) {} } } }); bot.on("end", () => { try { logger.warn(manualStopRequested ? "Bağlantı koptu. Manuel durdurma istendiği için yeniden bağlanılmayacak." : "Bağlantı koptu. Tüm zamanlayıcılar sıfırlanıyor ve YENİDEN BAĞLANMA SÜRECİ BAŞLIYOR..."); stopPeriodicHomeChecks(); stopStallWatcher(); lastCheckAtMs = null; botIsReady = false; baglantiTuru = "Yok"; botUptimeStart = null; isListeningChat = false; activeConnectionIp = "Bilinmiyor"; activeConnectionLocation = "Bilinmiyor"; nextScheduledCheckTimeStr = manualStopRequested ? "Bot manuel olarak durduruldu." : "Bağlantı koptuğu için planlama iptal edildi."; nextKaziciTimeStr = manualStopRequested ? "Bot manuel olarak durduruldu." : "Bağlantı koptuğu için planlama iptal edildi."; nextCiftciTimeStr = manualStopRequested ? "Bot manuel olarak durduruldu." : "Bağlantı koptuğu için planlama iptal edildi."; botActions.scanAndFeedManual = null; botActions.scanAndCollectMinersManual = null; botActions.openCiftciAndSellAllManual = null; botActions.goToIslandManual = null; botActions.sendSkyblockManual = null; botActions.antiBotMove = null; botActions.sendMoneyToNick = null; botActions.scanStorageInfoManual = null; botActions.processOrderManual = null; safeDestroyAllConnections(); if (!manualStopRequested && config.utils && config.utils["auto-reconnect"]) { triggerSafeReconnect(); } } catch (err) { logger.error(`['end' Hatası] Bağlantı kopma işlemi sırasında beklenmeyen hata: ${err && err.stack ? err.stack : err}`); } }); bot.on("kicked", (reason) => logger.warn(`Bot sunucudan atıldı: ${util.inspect(reason)}`)); bot.on("error", (err) => logger.error(`Mineflayer Hatası: ${err}`)); } catch (err) { logger.error(`[createBot Hatası] Beklenmeyen bir hata oluştu, bağlantı denemesi güvenle iptal edildi: ${err && err.stack ? err.stack : err}`); isConnecting = false; botIsReady = false; baglantiTuru = "Yok"; if (!manualStopRequested && config.utils && config.utils["auto-reconnect"]) { triggerSafeReconnect(); } } } 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(); 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] ${formatDurationMs(delay)} sonra tekil bir hat üzerinden sistem sıfırdan başlatılacak...`); reconnectTimeout = setTimeout(() => { createBot(); }, delay); } let pendingHardRestartTimeout = null; function stopBotHard(reason) { try { logger.warn(`[Manuel Kontrol] Bot durduruluyor. Sebep: ${reason || "Elle durdurma istendi"}`); manualStopRequested = true; if (reconnectTimeout) { clearTimeout(reconnectTimeout); reconnectTimeout = null; } if (pendingHardRestartTimeout) { clearTimeout(pendingHardRestartTimeout); pendingHardRestartTimeout = null; } stopPeriodicHomeChecks(); lastCheckAtMs = null; safeDestroyAllConnections(); botIsReady = false; baglantiTuru = "Yok"; botUptimeStart = null; activeConnectionIp = "Bilinmiyor"; activeConnectionLocation = "Bilinmiyor"; nextScheduledCheckTimeStr = "Bot manuel olarak durduruldu."; nextKaziciTimeStr = "Bot manuel olarak durduruldu."; nextCiftciTimeStr = "Bot manuel olarak durduruldu."; } catch (err) { logger.error(`[stopBotHard Hatası] ${err && err.stack ? err.stack : err}`); } } function startBotHard(reason) { try { logger.info(`[Manuel Kontrol] Bot başlatılıyor. Sebep: ${reason || "Elle başlatma istendi"}`); manualStopRequested = false; if (bot || isConnecting) { safeDestroyAllConnections(); } stopPeriodicHomeChecks(); createBot(); } catch (err) { logger.error(`[startBotHard Hatası] ${err && err.stack ? err.stack : err}`); } } function restartBotHard(reason) { logger.warn(`[Manuel Kontrol] Bot tamamen sıfırdan yeniden başlatılıyor (hard restart). Sebep: ${reason || "Elle restart istendi"}`); stopBotHard(reason); if (pendingHardRestartTimeout) { clearTimeout(pendingHardRestartTimeout); } pendingHardRestartTimeout = setTimeout(() => { pendingHardRestartTimeout = null; startBotHard(reason); }, 750); } 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("/logs", (req, res) => { const logs = getRecentLogs(); res.send({ server_time: new Date().toLocaleString('tr-TR'), range_minutes: 30, count: logs.length, logs: logs.map(l => ({ time: new Date(l.time).toLocaleTimeString('tr-TR'), level: l.level, message: l.message })) }); }); app.get("/logs/view", (req, res) => { const html = ` Sistem Logları

Sistem Logları son 30 dakika

`; res.send(html); }); app.get("/status", (req, res) => { let uptimeString = "Bağlı Değil"; if (botIsReady && botUptimeStart) { uptimeString = formatDurationMs(Date.now() - botUptimeStart); } 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, last_kazici_check: lastKaziciCheckTimeStr, next_kazici_check: nextKaziciTimeStr, last_ciftci_sell: lastCiftciSellTimeStr, next_ciftci_sell: nextCiftciTimeStr, last_order: lastOrderTimeStr, gui_lock_busy: guiMutex.isLocked(), manually_stopped: manualStopRequested, is_connecting: isConnecting, failover_mode_active: failoverModeActive }); }); app.get("/storageInfo", (req, res) => { res.send({ diamond: storageInfoCache.diamond, emerald: storageInfoCache.emerald, iron_block: storageInfoCache.iron_block, lastUpdated: storageInfoCache.lastUpdated }); }); app.get("/bot/stop", (req, res) => { stopBotHard("Web paneli üzerinden /bot/stop isteği"); res.send({ success: true, message: "Bot durduruldu. Tekrar başlatmak için /bot/start kullan." }); }); app.get("/bot/start", (req, res) => { if (bot && botIsReady) { return res.send({ success: false, message: "Bot zaten çalışıyor. Sıfırdan başlatmak için /bot/restart kullan." }); } startBotHard("Web paneli üzerinden /bot/start isteği"); res.send({ success: true, message: "Bot başlatılıyor." }); }); app.get("/bot/restart", (req, res) => { restartBotHard("Web paneli üzerinden /bot/restart isteği"); res.send({ success: true, message: "Bot tamamen sıfırdan yeniden başlatılıyor (soket/SSH dahil)." }); }); app.get("/bot/kazici-kontrol", (req, res) => { if (!bot || !botIsReady || !botActions.scanAndCollectMinersManual) { return res.status(503).send({ success: false, message: "Bot hazır değil." }); } res.send({ success: true, message: "Kazıcı kontrolü öncelikli olarak kuyruğa alındı, işleniyor." }); botActions.scanAndCollectMinersManual().catch(e => logger.error(`[/bot/kazici-kontrol Hatası] ${e && e.stack ? e.stack : e}`)); }); app.get("/bot/ciftci-satis", (req, res) => { if (!bot || !botIsReady || !botActions.openCiftciAndSellAllManual) { return res.status(503).send({ success: false, message: "Bot hazır değil." }); } res.send({ success: true, message: "Çiftçi satışı öncelikli olarak kuyruğa alındı, işleniyor." }); botActions.openCiftciAndSellAllManual().catch(e => logger.error(`[/bot/ciftci-satis Hatası] ${e && e.stack ? e.stack : e}`)); }); app.get("/bot/checkFarmer", (req, res) => { if (!bot || !botIsReady || !botActions.scanStorageInfoManual) { return res.status(503).send({ success: false, message: "Bot hazır değil." }); } res.send({ success: true, message: "Çiftçi depo/storage kontrolü öncelikli olarak kuyruğa alındı, işleniyor." }); botActions.scanStorageInfoManual().catch(e => logger.error(`[/bot/checkFarmer Hatası] ${e && e.stack ? e.stack : e}`)); }); app.get("/bot/besleyici-kontrol", (req, res) => { if (!bot || !botIsReady || !botActions.scanAndFeedManual) { return res.status(503).send({ success: false, message: "Bot hazır değil." }); } res.send({ success: true, message: "Besleyici kontrolü öncelikli olarak kuyruğa alındı, işleniyor." }); botActions.scanAndFeedManual().catch(e => logger.error(`[/bot/besleyici-kontrol Hatası] ${e && e.stack ? e.stack : e}`)); }); app.get("/bot/skyblock", (req, res) => { if (!bot || !botIsReady || !botActions.sendSkyblockManual) { return res.status(503).send({ success: false, message: "Bot hazır değil." }); } botActions.sendSkyblockManual().catch(e => logger.error(`[/bot/skyblock Hatası] ${e && e.stack ? e.stack : e}`)); res.send({ success: true, message: "/skyblock komutu gönderildi." }); }); app.get("/bot/is-go", (req, res) => { if (!bot || !botIsReady || !botActions.goToIslandManual) { return res.status(503).send({ success: false, message: "Bot hazır değil." }); } botActions.goToIslandManual().catch(e => logger.error(`[/bot/is-go Hatası] ${e && e.stack ? e.stack : e}`)); res.send({ success: true, message: `${GO_ISLAND_COMMAND} komutu gönderildi.` }); }); app.get("/bot/sendmsg", (req, res) => { if (!bot || !botIsReady) { return res.status(503).send({ success: false, message: "Bot hazır değil." }); } const message = req.query.message; if (!message || typeof message !== "string" || message.trim().length === 0) { return res.status(400).send({ success: false, message: "message query parametresi zorunlu." }); } try { bot.chat(message); logger.info(`[${bot.username}] /sendmsg üzerinden chat mesajı gönderildi: ${message}`); res.send({ success: true, message: "Mesaj gönderildi.", sent: message }); } catch (err) { logger.error(`[/bot/sendmsg Hatası] ${err && err.message ? err.message : err}`); res.status(500).send({ success: false, message: "Mesaj gönderilirken hata oluştu." }); } }); app.get("/bot/antibotmove", (req, res) => { if (!bot || !botIsReady) { return res.status(503).send({ success: false, message: "Bot hazır değil." }); } try { botActions.antiBotMove().catch(e => logger.error(`[/bot/antibotmove Hatası] ${e && e.stack ? e.stack : e}`)); res.send({ success: true, message: "Anti Bot Yürüyüşü Yapıldı."}); } catch (err) { logger.error(`[/bot/antibotmove Hatası] ${err && err.message ? err.message : err}`); res.status(500).send({ success: false, message: "Anti Bot Move sırasında hata oluştu." }); } }); app.get("/bot/sendmoney", (req, res) => { if (!bot || !botIsReady || !botActions.sendMoneyToNick) { return res.status(503).send({ success: false, message: "Bot hazır değil." }); } const nick = req.query.nick; if (!nick || typeof nick !== "string" || nick.trim().length === 0) { return res.status(400).send({ success: false, message: "nick query parametresi zorunlu." }); } botActions.sendMoneyToNick(nick) .then((result) => { if (result.success) { res.send(result); } else { res.status(500).send(result); } }) .catch((err) => { logger.error(`[/bot/sendmoney Hatası] ${err && err.stack ? err.stack : err}`); res.status(500).send({ success: false, message: "Beklenmeyen bir hata oluştu." }); }); }); app.get("/bot/order", (req, res) => { if (!bot || !botIsReady || !botActions.processOrderManual) { return res.status(503).send({ success: false, message: "Bot hazır değil." }); } const item = req.query.item; const nick = req.query.nick; if (!item || typeof item !== "string" || !ORDER_ALLOWED_ITEMS.includes(item)) { return res.status(400).send({ success: false, message: `item query parametresi zorunlu ve şunlardan biri olmalı: ${ORDER_ALLOWED_ITEMS.join(", ")}` }); } if (!nick || typeof nick !== "string" || nick.trim().length === 0) { return res.status(400).send({ success: false, message: "nick query parametresi zorunlu." }); } res.send({ success: true, message: "Sipariş işlemi öncelikli olarak kuyruğa alındı, işleniyor." }); botActions.processOrderManual(item, nick) .catch(e => logger.error(`[/bot/order Hatası] ${e && e.stack ? e.stack : e}`)); }); 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 = ` Canlı Minecraft Sohbeti

Canlı Oyun Sohbeti

`; 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); };