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