Files changed (2) hide show
  1. bot.js +1295 -292
  2. logging.js +65 -1
bot.js CHANGED
@@ -1,8 +1,5 @@
1
  const mineflayer = require("mineflayer");
2
  const mineflayerViewer = require('prismarine-viewer').mineflayer;
3
- const Movements = require("mineflayer-pathfinder").Movements;
4
- const pathfinder = require("mineflayer-pathfinder").pathfinder;
5
- const { GoalBlock, GoalXZ } = require("mineflayer-pathfinder").goals;
6
  const util = require("util");
7
  const { SocksClient } = require('socks');
8
  const { Client } = require('ssh2');
@@ -11,43 +8,96 @@ const dns = require('dns').promises;
11
  const config = require("./settings.json");
12
  const loggers = require("./logging.js");
13
  const logger = loggers.logger;
 
14
 
15
  const https = require('https');
16
 
 
 
 
 
 
 
 
 
 
17
  // --- AYARLAR ---
18
- const HOME_ARRIVAL_DELAY_MS = 2000;
19
- const POST_ACTION_DELAY_MS = 4000;
20
- const WINDOW_OPEN_TIMEOUT_MS = 6000;
21
  const DEFAULT_CHECK_INTERVAL_MS = 10 * 60 * 1000; // Varsayılan 10 dk
22
  const FULL_FEED_COOLDOWN_MS = 50 * 60 * 1000; // Besleme sonrası sabit 50 dk
23
- const GO_HOME_COMMAND = "/home";
24
- const RETURN_ISLAND_COMMAND = "/is home";
25
- const BESLEYICI_NAMES = (config.utils && config.utils["besleyici-names"]) || ["Besleyici", "ʙᴇsʟᴇʏiᴄi"];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  let viewerInstance = null;
27
  let viewerTimeout = null;
28
  let bot = null;
29
  let botIsReady = false;
30
  let baglantiTuru = "Yok";
31
- let activeMessages = [];
32
- let messagesTimeout = null;
33
- let isListeningChat = false;
34
  const ansiToHtmlConvert = new (require('ansi-to-html'))({ newline: true });
35
 
36
  // --- GÜVENLİ BAĞLANTI DEĞİŞKENLERİ ---
37
- let isConnecting = false;
38
- let isDestroying = false;
39
- let reconnectTimeout = null;
40
- let currentSshClient = null;
41
- let currentStream = null;
42
- let currentSocket = null;
43
- let windowOpenResolvers = [];
44
 
45
  // --- PANEL METRİKLERİ ---
46
- let botUptimeStart = null;
47
- let lastFullFeedTimeStr = "Henüz besleme yapılmadı";
48
- let lastCheckTimeStr = "Henüz kontrol yapılmadı";
49
  let nextScheduledCheckTimeStr = "Botun spawn olması bekleniyor...";
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  // --- DİNAMİK IP VE KONUM BİLGİLERİ ---
52
  let activeConnectionIp = "Bilinmiyor";
53
  let activeConnectionLocation = "Bilinmiyor";
@@ -55,11 +105,125 @@ let localExternalIp = "Bilinmiyor";
55
 
56
  // --- DİNAMİK ZAMANLAYICI KONTROLÜ ---
57
  let periodicTimeoutHandle = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
  async function getIpLocation(ip) {
60
  if (!ip || ip === "Bilinmiyor" || ip.startsWith("127.")) return "Bilinmiyor";
61
 
62
- // Denenecek servisler listesi
63
  const services = [
64
  `https://ip-api.com/json/${ip}?fields=status,country,city`,
65
  `https://ipapi.co/${ip}/json/`
@@ -74,11 +238,10 @@ async function getIpLocation(ip) {
74
  res.on('end', () => {
75
  try {
76
  const geo = JSON.parse(data);
77
- // Servise göre cevap yapısı değişebilir, burada basit bir kontrol yapıyoruz
78
  if (geo.country && geo.status !== "fail") {
79
  resolve(`${geo.country} / ${geo.city || geo.region || ""}`);
80
  } else {
81
- resolve(null); // Bir sonraki servise geç
82
  }
83
  } catch (e) { resolve(null); }
84
  });
@@ -109,12 +272,14 @@ async function createBot() {
109
  logger.warn("[Sistem] Zaten aktif bir bağlantı süreci yürütülüyor. Mükerrer istek engellendi.");
110
  return;
111
  }
112
- isConnecting = true;
113
- isDestroying = false;
 
 
114
 
115
  const net = require('net');
116
  const sshClient = new Client();
117
- currentSshClient = sshClient;
118
 
119
  const targetServerIp = config.server.ip;
120
  const targetServerPort = config.server.port;
@@ -132,27 +297,28 @@ async function createBot() {
132
  let isFinalFailureHandled = false;
133
 
134
  const handleFinalFailure = (reason) => {
135
- if (isFinalFailureHandled) return;
136
  isFinalFailureHandled = true;
137
 
138
  logger.error(`[Kritik Hata] ${reason}. Bağlantı sağlanamadı! Sistem en başa döndürülüyor...`);
139
-
140
  baglantiTuru = "Yok";
141
  botIsReady = false;
142
- botUptimeStart = null;
143
  activeConnectionIp = "Bilinmiyor";
144
  activeConnectionLocation = "Bilinmiyor";
145
 
146
  stopPeriodicHomeChecks();
147
- safeDestroyAllConnections();
 
148
 
149
- if (config.utils && config.utils["auto-reconnect"]) {
150
  triggerSafeReconnect();
151
  }
152
  };
153
 
154
  const connectDirectly = async (reason) => {
155
- if (fallbackTriggered) return;
156
  fallbackTriggered = true;
157
 
158
  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...`);
@@ -161,8 +327,8 @@ async function createBot() {
161
  try {
162
  sshClient.removeAllListeners('ready');
163
  sshClient.removeAllListeners('error');
164
- sshClient.destroy();
165
- } catch (e) {}
166
  }
167
 
168
  activeConnectionIp = localExternalIp !== "Bilinmiyor" ? localExternalIp : "Kendi Dış IP'niz";
@@ -172,14 +338,14 @@ async function createBot() {
172
  host: targetServerIp,
173
  port: parseInt(targetServerPort) || 25565
174
  });
175
- currentSocket = socket;
176
 
177
  let isDirectHandled = false;
178
 
179
  const directTimeout = setTimeout(() => {
180
  if (isDirectHandled) return;
181
  isDirectHandled = true;
182
-
183
  socket.destroy();
184
  handleFinalFailure("Kendi IP bağlantısı 1 dakikalık süre içinde zaman aşımına uğradı (Sunucu yanıt vermedi)");
185
  }, 60000);
@@ -187,7 +353,7 @@ async function createBot() {
187
  socket.on('connect', () => {
188
  if (isDirectHandled) return;
189
  isDirectHandled = true;
190
-
191
  clearTimeout(directTimeout);
192
  logger.info("[Bağlantı Yedekleme] Kendi IP'miz üzerinden GERÇEK TCP bağlantısı başarıyla kuruldu!");
193
  baglantiTuru = "Normal";
@@ -205,7 +371,7 @@ async function createBot() {
205
  logger.error(`[Bağlantı Yedekleme Hatası] Sunucu bağlantıyı reddetti veya olumsuz yanıt verdi: ${err.message}`);
206
  handleFinalFailure(`Kendi IP hatası: ${err.message}`);
207
  });
208
-
209
  socket.on('close', () => {
210
  if (isDirectHandled) return;
211
  isDirectHandled = true;
@@ -215,6 +381,7 @@ async function createBot() {
215
  });
216
  };
217
 
 
218
  const githubRawUrl = 'https://raw.githubusercontent.com/suvarileader/che/refs/heads/main/doc/ower/doc.json';
219
  logger.info("[GitHub Config] Güncel SSH tünel bilgileri GitHub'dan çekiliyor...");
220
 
@@ -279,46 +446,63 @@ async function createBot() {
279
  logger.info('[SSH Tünel] SSH bağlantısı başarılı. Minecraft sunucusuna tünel açılıyor...');
280
 
281
  try {
282
- const lookup = await dns.lookup(sshConfig.host);
283
- activeConnectionIp = lookup.address;
284
- } catch (e) {
285
- activeConnectionIp = sshConfig.host;
286
- }
287
- activeConnectionLocation = await getIpLocation(activeConnectionIp);
288
-
289
- sshClient.forwardOut(
290
- '127.0.0.1',
291
- 12345,
292
- targetServerIp,
293
- parseInt(targetServerPort) || 25565,
294
- (err, stream) => {
295
- if (err) {
296
- connectDirectly(`Tünel yönlendirme hatası: ${err.message}`);
297
- return;
298
- }
299
-
300
- logger.info("[SSH Tünel] Tünel başarıyla kuruldu! Mineflayer sokete bağlanıyor.");
301
- baglantiTuru = "SSH";
302
- currentStream = stream;
303
-
304
- stream.once('close', () => {
305
- if (!fallbackTriggered && !isDestroying) {
306
- connectDirectly("Tünel stream kapandı");
307
- }
308
- });
309
-
310
- stream.once('error', (e) => {
311
- if (!fallbackTriggered && !isDestroying) {
312
- connectDirectly(`Stream hatası: ${e.message}`);
313
  }
314
- });
315
 
316
- if (client && typeof client.setSocket === 'function') {
317
- client.setSocket(stream);
318
- client.emit('connect');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  }
 
 
 
 
320
  }
321
- );
322
  });
323
 
324
  sshClient.on('error', (err) => {
@@ -334,28 +518,17 @@ async function createBot() {
334
  clearTimeout(connectionTimeout);
335
  connectDirectly(`Sistem Hatası: ${err.message}`);
336
  }
 
 
 
 
 
 
337
  }
338
  });
339
 
340
- bot.loadPlugin(pathfinder);
341
 
342
  let lastFullFeedAt = 0;
343
- let activeTimeouts = [];
344
-
345
- function setSafeTimeout(fn, delay) {
346
- const t = setTimeout(fn, delay);
347
- activeTimeouts.push(t);
348
- return t;
349
- }
350
-
351
- function stopPeriodicHomeChecks() {
352
- if (periodicTimeoutHandle) {
353
- clearTimeout(periodicTimeoutHandle);
354
- periodicTimeoutHandle = null;
355
- }
356
- activeTimeouts.forEach(t => clearTimeout(t));
357
- activeTimeouts = [];
358
- }
359
 
360
  function getExtendedDateTimeString() {
361
  const now = new Date();
@@ -396,15 +569,45 @@ async function createBot() {
396
  if (!s || typeof s !== "string") return "";
397
  let t = stripColorCodes(s);
398
  if (t.normalize) t = t.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
399
- return t.toLowerCase();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  }
401
 
402
- const TARGET_NAME_NORMS = BESLEYICI_NAMES.map(n => normalizeForMatch(n));
403
 
404
  function isBesleyiciName(name) {
405
- if (!name) return false;
406
- const norm = normalizeForMatch(name);
407
- return TARGET_NAME_NORMS.some(t => norm.includes(t));
 
 
 
 
 
 
 
 
 
 
408
  }
409
 
410
  function safeGet(obj, pathArray) {
@@ -418,14 +621,13 @@ async function createBot() {
418
  } catch (e) { return undefined; }
419
  }
420
 
421
- function deepSearchForString(root, targetLower) {
422
  const results = [];
423
  const visited = new WeakSet();
424
  function recurse(obj, path) {
425
  if (obj == null || typeof obj === "function") return;
426
  if (typeof obj === "string") {
427
- const norm = normalizeForMatch(obj);
428
- if (norm.includes(targetLower)) results.push({ path, value: obj });
429
  return;
430
  }
431
  if (typeof obj !== "object") return;
@@ -487,44 +689,139 @@ async function createBot() {
487
  } catch (err) { return null; }
488
  }
489
 
490
- async function goHome() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
  if (!bot || !botIsReady) return;
492
- logger.info(`[${currentBotUsername}] ${GO_HOME_COMMAND} komutu gönderiliyor.`);
493
- bot.chat(GO_HOME_COMMAND);
 
 
 
 
494
  }
495
 
496
- async function returnIsland() {
497
  if (!bot || !botIsReady) return;
498
- logger.info(`[${currentBotUsername}] ${RETURN_ISLAND_COMMAND} komutu gönderiliyor.`);
499
- bot.chat(RETURN_ISLAND_COMMAND);
 
 
 
 
500
  }
501
 
502
- // --- DİNAMİK PLANLAMA MOTORU ---
503
  function scheduleNextCheck(delayMs) {
504
  if (periodicTimeoutHandle) {
505
  clearTimeout(periodicTimeoutHandle);
506
  }
507
-
508
- nextScheduledCheckTimeStr = `${getFutureDateTimeString(delayMs)} (${Math.round(delayMs / 60 / 1000)} dk sonra)`;
509
- logger.info(`[Planlayıcı] Bir sonraki döngü planlandı. Zaman: ${nextScheduledCheckTimeStr}`);
 
510
 
511
  periodicTimeoutHandle = setTimeout(async () => {
512
  if (!bot || !botIsReady) return;
513
-
514
- await goHome();
515
- await new Promise(r => setTimeout(r, HOME_ARRIVAL_DELAY_MS));
516
- await scanAndFeed();
517
- await new Promise(r => setTimeout(r, POST_ACTION_DELAY_MS));
518
- await returnIsland();
 
 
 
 
 
 
 
 
519
  }, delayMs);
520
  }
521
 
522
- async function scanAndFeed() {
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  if (!bot || !botIsReady) return;
524
-
525
- // Sunucu lagı yüzünden döngü üst üste tetiklenirse koruma
 
 
 
526
  if (Date.now() < (lastFullFeedAt + 30000)) {
527
- logger.warn("[Planlayıcı] Çok kısa süre önce işlem yapıldı. Güvenlik kilidi devrede.");
 
 
 
 
 
 
528
  return;
529
  }
530
 
@@ -537,20 +834,22 @@ async function createBot() {
537
  best = { entity: e, path: "displayName/name" };
538
  break;
539
  }
540
- for (const target of TARGET_NAME_NORMS) {
541
- const deep = deepSearchForString(e, target);
542
- if (deep.length > 0) {
543
- best = { entity: e, path: deep[0].path };
544
- break;
545
- }
546
  }
547
- if (best) break;
548
  }
549
 
550
  if (!best) {
551
  logger.info(`[${currentBotUsername}] /home sonrası tarama: besleyici minyon bulunamadı.`);
552
  bot.chat("/skyblock");
553
- // Okuyamadığımız için 10 dakika sonra tekrar deneyecek şekilde planla
 
 
 
 
 
554
  scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
555
  return;
556
  }
@@ -565,189 +864,689 @@ async function createBot() {
565
  return;
566
  }
567
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
  try {
569
- if (bot.lookAt) await bot.lookAt(best.entity.position.offset(0, 1.0, 0), true);
570
- await new Promise(r => setTimeout(r, 400));
571
-
572
- const startTime = Date.now();
573
- const endTime = startTime + WINDOW_OPEN_TIMEOUT_MS;
574
- let clickCount = 0;
575
- let isLoopActive = true;
576
-
577
- await new Promise((resolve) => {
578
- const cleanFinish = () => {
579
- isLoopActive = false;
580
- clearInterval(clickInterval);
581
- windowOpenResolvers = [];
582
- resolve();
583
- };
584
-
585
- windowOpenResolvers.push(() => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
586
  cleanFinish();
587
- });
 
588
 
589
- const sendClickPacket = () => {
590
- if (!isLoopActive) return;
 
 
 
 
591
 
592
- if (bot.currentWindow != null) {
593
- cleanFinish();
594
- return;
595
- }
596
 
597
- if (Date.now() > endTime) {
598
- logger.warn(`[Tıklama Sistemi] ${WINDOW_OPEN_TIMEOUT_MS} ms doldu ancak menü sunucudan gelmedi. Pes ediliyor.`);
599
- cleanFinish();
600
- scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
601
- return;
602
- }
603
 
604
- clickCount++;
605
- logger.info(`[Tıklama Sistemi] Menü tespiti başarısız. Deneme #${clickCount} gönderiliyor... (id:${best.entity.id})`);
606
-
607
- try {
608
- if (typeof bot.activateEntityAt === "function") bot.activateEntityAt(best.entity, best.entity.position);
609
- else if (typeof bot.activateEntity === "function") bot.activateEntity(best.entity);
610
- } catch (err) {}
611
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
 
613
- sendClickPacket();
614
- const clickInterval = setInterval(sendClickPacket, 600);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
  });
616
 
617
- } catch (err) {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  }
619
 
620
  bot.once("spawn", async () => {
621
- isConnecting = false;
622
  if (!bot) return;
623
- bot.pathfinder.setMovements(new Movements(bot));
624
- logger.info("Bot sunucuya giriş yaptı.");
625
- botIsReady = true;
626
- botUptimeStart = Date.now();
627
-
628
- if (config.utils && config.utils["auto-auth"] && config.utils["auto-auth"].enabled) {
629
- const password = config.utils["auto-auth"].password;
630
-
631
- setSafeTimeout(() => {
632
- if (bot && botIsReady) {
633
- bot.chat(`/login ${password}`);
634
- logger.info(`[${currentBotUsername}] Login komutu kullanıldı.`);
635
- }
636
- }, 2500);
637
 
638
- setSafeTimeout(() => {
639
- if (bot && botIsReady) {
640
- bot.chat(`/skyblock`);
641
- logger.info(`[${currentBotUsername}] Skyblock'a ışınlanma komutu kullanıldı.`);
642
- }
643
- }, 7500);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
 
645
- // İlk çalıştırma: Girişten 12.5 saniye sonra ilk kontrolü yap
646
- setSafeTimeout(async () => {
647
- if (!bot || !botIsReady) return;
648
- await goHome();
649
- await new Promise(r => setTimeout(r, HOME_ARRIVAL_DELAY_MS));
650
- await scanAndFeed();
651
- await new Promise(r => setTimeout(r, POST_ACTION_DELAY_MS));
652
- await returnIsland();
653
- }, 12500);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
  }
655
  });
656
 
657
  bot.on("windowOpen", async (window) => {
658
  if (!bot) return;
659
- const title = parseWindowTitleToString(window.title).toLowerCase();
660
- if (!title.includes("besleyici") && !parseWindowTitleToString(window.title).includes("ʙᴇsʟᴇʏiᴄi")) {
661
- logger.info(`[${currentBotUsername}] Bi chest açıldı ama window title ı uyuşmadığı için kapatıldı. ${parseWindowTitleToString(window.title)}`);
662
- try { bot.closeWindow(window); } catch(e){}
663
- return;
664
- }
665
- const beefSlot = window.slots.find(s => s?.name === "cooked_beef");
666
- const can = extractMinyonCanFromSlot(beefSlot);
667
-
668
- if (can != null) {
669
- const currentStamp = getExtendedDateTimeString();
670
- lastCheckTimeStr = `${currentStamp} (Okunan Can: ${can})`;
671
-
672
- if (can <= 2) {
673
- // CAN 2 VEYA ALTINDA: BESLE VE SABİT 50 DK PLANLA
674
- try {
675
- logger.info(`[${currentBotUsername}] Minyon canı ${can} olduğu için besleniyor...`);
676
- await bot.clickWindow(36, 0, 0);
677
- lastFullFeedAt = Date.now();
678
- lastFullFeedTimeStr = `${currentStamp} (Minyon canı ${can} iken başarıyla beslendi)`;
679
- logger.info(`[${currentBotUsername}] Tıklama onaylandı. Fixed 50dk bekleme aktif.`);
680
- } catch(e) {
681
- logger.error(`[Arayüz Hatası] Tıklama işlemi sunucu tarafından onaylanmadı: ${e.message}`);
682
  }
683
- scheduleNextCheck(FULL_FEED_COOLDOWN_MS);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
 
685
  } else {
686
- // CAN 3 VEYA DAHA YÜKSEK: DİNAMİK PLANLAMA YAP
687
- // Formül: (Can - 2) * 15 dakika. Maksimum 65 dakika.
688
- let calculatedMinutes = (can - 2) * 15;
689
- if (calculatedMinutes > 50) calculatedMinutes = 65;
690
- if (calculatedMinutes < 15) calculatedMinutes = 15;
691
-
692
- const dynamicDelayMs = calculatedMinutes * 60 * 1000;
693
-
694
- logger.info(`[${currentBotUsername}] Minyon canı ${can} olarak okundu. Canın 2'ye düşmesi için ${calculatedMinutes} dakika beklenecek.`);
695
-
696
- if (can >= 10) {
697
- lastFullFeedTimeStr = `${currentStamp} (Canı ${can} olduğu için besleme gerekmedi)`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
698
  }
699
-
700
- scheduleNextCheck(dynamicDelayMs);
701
  }
702
- } else {
703
- // Can okunamadıysa güvenlik amacıyla 10 dk sonra tekrar kontrol et
704
- logger.warn(`[${currentBotUsername}] Menü açıldı ama can okunamadı. 10 dk sonra tekrar denenecek.`);
705
- scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
706
  }
707
 
708
- try { bot.closeWindow(window); } catch(e){}
709
-
710
  while (windowOpenResolvers.length > 0) {
711
  const fn = windowOpenResolvers.shift();
712
- if (typeof fn === 'function') fn();
 
 
713
  }
714
  });
715
 
716
  bot.on("end", () => {
717
- logger.warn("Bağlantı koptu. Tüm zamanlayıcılar sıfırlanıyor ve YENİDEN BAĞLANMA SÜRECİ BAŞLIYOR...");
718
- stopPeriodicHomeChecks();
719
- botIsReady = false;
720
- baglantiTuru = "Yok";
721
- botUptimeStart = null;
722
- isListeningChat = false;
723
- activeConnectionIp = "Bilinmiyor";
724
- activeConnectionLocation = "Bilinmiyor";
725
- nextScheduledCheckTimeStr = "Bağlantı koptuğu için planlama iptal edildi.";
726
-
727
- safeDestroyAllConnections();
 
 
 
 
 
 
 
 
728
 
729
- if (config.utils && config.utils["auto-reconnect"]) {
730
- triggerSafeReconnect();
 
 
 
 
 
731
  }
732
  });
733
 
734
  bot.on("kicked", (reason) => logger.warn(`Bot sunucudan atıldı: ${util.inspect(reason)}`));
735
  bot.on("error", (err) => logger.error(`Mineflayer Hatası: ${err}`));
 
 
 
 
 
 
 
 
 
 
 
736
  }
737
 
738
  function safeDestroyAllConnections() {
739
  if (isDestroying) return;
740
- isDestroying = true;
741
- isConnecting = false;
742
-
743
  windowOpenResolvers = [];
744
 
745
  if (viewerInstance && typeof viewerInstance.close === 'function') {
746
- try {
747
  if (viewerInstance.ws && typeof viewerInstance.ws.close === 'function') {
748
  viewerInstance.ws.close();
749
  }
750
- viewerInstance.close();
751
  } catch(e){}
752
  }
753
  viewerInstance = null;
@@ -758,7 +1557,7 @@ function safeDestroyAllConnections() {
758
  }
759
 
760
  if (currentSshClient && typeof currentSshClient.destroy === 'function') {
761
- try { currentSshClient.destroy(); } catch(e){}
762
  } else {
763
  if (currentSocket && typeof currentSocket.destroy === 'function') {
764
  try { currentSocket.destroy(); } catch(e){}
@@ -771,12 +1570,9 @@ function safeDestroyAllConnections() {
771
 
772
  if (bot) {
773
  try {
774
- bot.removeAllListeners('message');
775
- bot.removeAllListeners('spawn');
776
- bot.removeAllListeners('windowOpen');
777
- bot.removeAllListeners('end');
778
- bot.quit();
779
- } catch(e){}
780
  bot = null;
781
  }
782
  }
@@ -786,13 +1582,77 @@ function triggerSafeReconnect() {
786
  clearTimeout(reconnectTimeout);
787
  }
788
  const delay = (config.utils && config.utils["auto-reconnect-delay"]) || 5000;
789
- logger.info(`[Sistem] ${delay} ms sonra tekil bir hat üzerinden sistem sıfırdan başlatılacak...`);
790
-
791
  reconnectTimeout = setTimeout(() => {
792
- createBot();
793
  }, delay);
794
  }
795
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
  function globalMinecraftMessageListener(jsonMsg) {
797
  try {
798
  if (!bot) return;
@@ -818,32 +1678,175 @@ const express = require("express");
818
  const app = express();
819
  const http = require('http');
820
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
821
  app.get("/status", (req, res) => {
822
  let uptimeString = "Bağlı Değil";
823
  if (botIsReady && botUptimeStart) {
824
- const diffMs = Date.now() - botUptimeStart;
825
- const diffSecs = Math.floor(diffMs / 1000);
826
- const days = Math.floor(diffSecs / 86400);
827
- const hours = Math.floor((diffSecs % 86400) / 3600);
828
- const mins = Math.floor((diffSecs % 3600) / 60);
829
- const secs = diffSecs % 60;
830
-
831
- uptimeString = `${days} Gün, ${hours} Saat, ${mins} Dakika, ${secs} Saniye`;
832
- }
833
-
834
- res.send({
835
- ready: botIsReady,
836
  username: bot ? bot.username : null,
837
  connection_type: baglantiTuru,
838
  connection_ip: activeConnectionIp,
839
  connection_geo: activeConnectionLocation,
840
  bot_uptime: uptimeString,
841
- last_minyon_check: lastCheckTimeStr,
842
  last_minyon_feed: lastFullFeedTimeStr,
843
- next_scheduled_check: nextScheduledCheckTimeStr // Panel için sonraki kontrol saati
 
 
 
 
 
 
 
844
  });
845
  });
846
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
847
  app.get("/", (req, res) => res.send("Bot Aktif"));
848
 
849
  app.get("/messages", (req, res) => {
@@ -870,7 +1873,7 @@ app.get("/messages", (req, res) => {
870
  messagesTimeout = setTimeout(() => {
871
  activeMessages = [];
872
  messagesTimeout = null;
873
-
874
  if (bot && isListeningChat) {
875
  bot.removeListener('message', globalMinecraftMessageListener);
876
  isListeningChat = false;
@@ -941,7 +1944,7 @@ const checkAndStartViewer = (req, res, next) => {
941
  if (!viewerInstance && isBotReady) {
942
  try {
943
  mineflayerViewer(bot, { port: 3000, firstPerson: true });
944
- viewerInstance = bot.viewer;
945
  logger.info("[Viewer] 3D Harita motoru arka planda (3000) başlatıldı.");
946
  setTimeout(() => next(), 600);
947
  return;
@@ -984,16 +1987,16 @@ app.use('/3dview', checkAndStartViewer, (req, res) => {
984
 
985
  app.get("/restart", (req, res) => {
986
  logger.info("[Sistem] Web paneli üzerinden tam restart isteği geldi. Sistem felç ediliyor...");
987
-
988
  if (config.utils) {
989
  config.utils["auto-reconnect"] = false;
990
  }
991
- isDestroying = true;
992
 
993
  if (bot && botIsReady) {
994
  try { bot.chat("Bot web paneli uzerinden tum kodlariyla yeniden baslatiliyor..."); } catch(e){}
995
  res.send({ success: true, message: "Tüm proje kapatıldı. Konteyner birkaç saniye içinde sıfırdan başlayacak." });
996
-
997
  setTimeout(() => {
998
  try { bot.quit(); } catch(e){}
999
  safeDestroyAllConnections();
 
1
  const mineflayer = require("mineflayer");
2
  const mineflayerViewer = require('prismarine-viewer').mineflayer;
 
 
 
3
  const util = require("util");
4
  const { SocksClient } = require('socks');
5
  const { Client } = require('ssh2');
 
8
  const config = require("./settings.json");
9
  const loggers = require("./logging.js");
10
  const logger = loggers.logger;
11
+ const getRecentLogs = loggers.getRecentLogs;
12
 
13
  const https = require('https');
14
 
15
+ // --- KÜRESEL GÜVENLİK AĞI ---
16
+ process.on('unhandledRejection', (reason) => {
17
+ logger.error(`[Küresel Güvenlik Ağı] Yakalanmamış Promise hatası (unhandledRejection): ${util.inspect(reason)}`);
18
+ });
19
+
20
+ process.on('uncaughtException', (err) => {
21
+ logger.error(`[Küresel Güvenlik Ağı] Yakalanmamış istisna (uncaughtException): ${err && err.stack ? err.stack : err}`);
22
+ });
23
+
24
  // --- AYARLAR ---
25
+ const HOME_ARRIVAL_DELAY_MS = 4000;
26
+ const WINDOW_OPEN_TIMEOUT_MS = 6000;
 
27
  const DEFAULT_CHECK_INTERVAL_MS = 10 * 60 * 1000; // Varsayılan 10 dk
28
  const FULL_FEED_COOLDOWN_MS = 50 * 60 * 1000; // Besleme sonrası sabit 50 dk
29
+ const GO_ISLAND_COMMAND = "/is go";
30
+ const BESLEYICI_NAMES_RAW = ["Besleyici", "ʙᴇsʟᴇʏiᴄi"];
31
+ const BESLEYICI_NAMES = Array.isArray(BESLEYICI_NAMES_RAW) ? BESLEYICI_NAMES_RAW : [BESLEYICI_NAMES_RAW];
32
+
33
+ const KAZICI_NAMES_RAW = (config.utils && (config.utils["kazici-names"] || config.utils["kazici-name"])) || ["Kazıcı", "ᴋᴀᴢıᴄı", "ᴋᴀᴢɪᴄɪ"];
34
+ const KAZICI_NAMES = Array.isArray(KAZICI_NAMES_RAW) ? KAZICI_NAMES_RAW : [KAZICI_NAMES_RAW];
35
+
36
+ const KAZICI_STORAGE_TRANSFER_SLOT = 17;
37
+ const KAZICI_BETWEEN_DELAY_MS = 5 * 1000;
38
+ const KAZICI_ACTION_SETTLE_DELAY_MS = 1200;
39
+
40
+ // --- ÇİFTÇİ (HEPSİNİ SAT) MENÜSÜ İÇİN AYARLAR ---
41
+ const CIFTCI_NAMES_RAW = (config.utils && (config.utils["ciftci-names"] || config.utils["ciftci-name"])) || ["Çiftçi", "çiꜰᴛçi"];
42
+ const CIFTCI_NAMES = Array.isArray(CIFTCI_NAMES_RAW) ? CIFTCI_NAMES_RAW : [CIFTCI_NAMES_RAW];
43
+ const CIFTCI_SELL_ALL_SLOT = 45;
44
+ const CIFTCI_COMMAND = "/çiftçi menü";
45
+ const CIFTCI_WINDOW_TIMEOUT_MS = 6000;
46
+ const CIFTCI_LORE_FALLBACK_KEYWORDS = ["hepsini sat"];
47
+ // Menü açıldıktan sonra sunucunun içeriği (lore/slot) tam yerleştirmesi için
48
+ // beklenecek süre, tıklamadan önce. Ardından art arda birkaç kez tıklanır.
49
+ const CIFTCI_CLICK_DELAY_AFTER_OPEN_MS = 1000;
50
+ const CIFTCI_CLICK_REPEAT_COUNT = 3;
51
+ const CIFTCI_CLICK_REPEAT_GAP_MS = 350;
52
+
53
+ // --- KAZICI PERİYODİK DÖNGÜ AYARLARI (bağımsız, 5 dakikada bir) ---
54
+ const KAZICI_CYCLE_INTERVAL_MS = 5 * 60 * 1000; // 5 dakika
55
+
56
+ // --- ÇİFTÇİ PERİYODİK DÖNGÜ AYARLARI (bağımsız, 1 dakikada bir) ---
57
+ const CIFTCI_CYCLE_INTERVAL_MS = 60 * 1000; // 1 dakika
58
+
59
  let viewerInstance = null;
60
  let viewerTimeout = null;
61
  let bot = null;
62
  let botIsReady = false;
63
  let baglantiTuru = "Yok";
64
+ let activeMessages = [];
65
+ let messagesTimeout = null;
66
+ let isListeningChat = false;
67
  const ansiToHtmlConvert = new (require('ansi-to-html'))({ newline: true });
68
 
69
  // --- GÜVENLİ BAĞLANTI DEĞİŞKENLERİ ---
70
+ let isConnecting = false;
71
+ let isDestroying = false;
72
+ let reconnectTimeout = null;
73
+ let currentSshClient = null;
74
+ let currentStream = null;
75
+ let currentSocket = null;
76
+ let windowOpenResolvers = [];
77
 
78
  // --- PANEL METRİKLERİ ---
79
+ let botUptimeStart = null;
80
+ let lastFullFeedTimeStr = "Henüz besleme yapılmadı";
81
+ let lastCheckTimeStr = "Henüz kontrol yapılmadı";
82
  let nextScheduledCheckTimeStr = "Botun spawn olması bekleniyor...";
83
 
84
+ // --- KAZICI PANEL METRİKLERİ (bağımsız) ---
85
+ let lastKaziciCheckTimeStr = "Henüz kazıcı kontrolü yapılmadı";
86
+ let nextKaziciTimeStr = "Botun spawn olması bekleniyor...";
87
+ let nextKaziciAtMs = null;
88
+
89
+ // --- ÇİFTÇİ PANEL METRİKLERİ (bağımsız) ---
90
+ let lastCiftciSellTimeStr = "Henüz çiftçi satışı yapılmadı";
91
+ let nextCiftciTimeStr = "Botun spawn olması bekleniyor...";
92
+ let nextCiftciAtMs = null;
93
+
94
+ // --- ÇİFTÇİ/KAZICI PERİYODİK DÖNGÜ KONTROLÜ ---
95
+ let lastCheckAtMs = null; // Besleyici için (panel bilgisi amaçlı, artık kazıcı/çiftçiyi etkilemiyor)
96
+ let nextScheduledCheckAtMs = null; // Besleyici için
97
+ let kaziciIntervalHandle = null; // Kazıcı bağımsız döngü zamanlayıcısı (5 dk)
98
+ let ciftciIntervalHandle = null; // Çiftçi bağımsız döngü zamanlayıcısı (1 dk)
99
+ let isMainCycleRunning = false; // Besleyici (goToIsland->besle) ana döngüsü sürüyor mu
100
+
101
  // --- DİNAMİK IP VE KONUM BİLGİLERİ ---
102
  let activeConnectionIp = "Bilinmiyor";
103
  let activeConnectionLocation = "Bilinmiyor";
 
105
 
106
  // --- DİNAMİK ZAMANLAYICI KONTROLÜ ---
107
  let periodicTimeoutHandle = null;
108
+ let activeTimeouts = [];
109
+
110
+ function setSafeTimeout(fn, delay) {
111
+ const t = setTimeout(fn, delay);
112
+ activeTimeouts.push(t);
113
+ return t;
114
+ }
115
+
116
+ // --- ÖNCELİKLİ GUI KİLİDİ (MUTEX) ---
117
+ // Besleyici, kazıcı ve çiftçi menüleri hepsi aynı windowOpen/tıklama mekanizmasını
118
+ // paylaştığı için ASLA aynı anda çalışmamalılar. Bu kilit ikisi arasında ayrım yapar:
119
+ // - tryAcquire(): periyodik/otomatik döngüler için. Kilit doluysa BEKLEMEZ, hemen
120
+ // false döner ve o tur sessizce atlanır (bir sonraki periyotta tekrar dener).
121
+ // - acquire(): Express panelinden (manuel) gelen istekler için. Kilit doluysa
122
+ // KUYRUĞA GİRER ve önceliklidir — mevcut işlem bitince, kuyrukta bekleyen varsa
123
+ // bile (ki otomatik döngüler hiç kuyruğa girmediği için pratikte tek bekleyen
124
+ // genelde manuel istek olur) ilk manuel istek işi devralır. Böylece kullanıcı
125
+ // panelden bir istek yaptığında, o sırada süren otomatik bir işlem varsa önce
126
+ // o tamamlanır, hemen ardından kullanıcının isteği en öncelikli şekilde işlenir.
127
+ class PriorityGuiMutex {
128
+ constructor() {
129
+ this._locked = false;
130
+ this._waiters = []; // manuel (öncelikli) bekleyenler, FIFO
131
+ }
132
+ isLocked() { return this._locked; }
133
+ tryAcquire() {
134
+ if (this._locked) return false;
135
+ this._locked = true;
136
+ return true;
137
+ }
138
+ acquire() {
139
+ if (!this._locked) {
140
+ this._locked = true;
141
+ return Promise.resolve();
142
+ }
143
+ return new Promise((resolve) => {
144
+ this._waiters.push(resolve);
145
+ });
146
+ }
147
+ release() {
148
+ if (this._waiters.length > 0) {
149
+ const next = this._waiters.shift();
150
+ // Kilit devrediliyor (locked=true kalır), bir sonraki bekleyen devralır.
151
+ next();
152
+ } else {
153
+ this._locked = false;
154
+ }
155
+ }
156
+ }
157
+ const guiMutex = new PriorityGuiMutex();
158
+
159
+ // Express panelinden diğer bot fonksiyonlarına erişim için (createBot() closure'ı
160
+ // dışından çağrılabilmeleri için köprü). Bot her (yeniden) bağlandığında doldurulur.
161
+ let botActions = {
162
+ scanAndFeedManual: null,
163
+ scanAndCollectMinersManual: null,
164
+ openCiftciAndSellAllManual: null,
165
+ goToIslandManual: null,
166
+ sendSkyblockManual: null,
167
+ };
168
+
169
+ // --- ORTAK SÜRE FORMATLAMA FONKSİYONU ---
170
+ // Tüm loglardaki "X dk sonra", "Y saat sonra" gibi elle yazılmış ifadelerin yerine
171
+ // bu fonksiyon kullanılır. Böylece yukarıdaki ayar sabitleri (KAZICI_CYCLE_INTERVAL_MS,
172
+ // CIFTCI_CYCLE_INTERVAL_MS, DEFAULT_CHECK_INTERVAL_MS, vb.) değiştirildiğinde,
173
+ // loglara yansıyan süre ifadeleri OTOMATİK olarak güncellenir; elle metin
174
+ // değiştirmeye gerek kalmaz.
175
+ function formatDurationMs(ms) {
176
+ if (ms == null || isNaN(ms) || ms < 0) return "0 saniye";
177
+ let totalSeconds = Math.round(ms / 1000);
178
+
179
+ const days = Math.floor(totalSeconds / 86400);
180
+ totalSeconds -= days * 86400;
181
+ const hours = Math.floor(totalSeconds / 3600);
182
+ totalSeconds -= hours * 3600;
183
+ const minutes = Math.floor(totalSeconds / 60);
184
+ const seconds = totalSeconds - minutes * 60;
185
+
186
+ const parts = [];
187
+ if (days > 0) parts.push(`${days} gün`);
188
+ if (hours > 0) parts.push(`${hours} saat`);
189
+ if (minutes > 0) parts.push(`${minutes} dakika`);
190
+ if (seconds > 0 || parts.length === 0) parts.push(`${seconds} saniye`);
191
+
192
+ return parts.join(" ");
193
+ }
194
+
195
+ function stopPeriodicHomeChecks() {
196
+ if (periodicTimeoutHandle) {
197
+ clearTimeout(periodicTimeoutHandle);
198
+ periodicTimeoutHandle = null;
199
+ }
200
+ activeTimeouts.forEach(t => clearTimeout(t));
201
+ activeTimeouts = [];
202
+
203
+ if (kaziciIntervalHandle) {
204
+ clearInterval(kaziciIntervalHandle);
205
+ kaziciIntervalHandle = null;
206
+ logger.info("[Kazıcı Periyodik] Döngü zamanlayıcısı durduruldu.");
207
+ }
208
+ if (ciftciIntervalHandle) {
209
+ clearInterval(ciftciIntervalHandle);
210
+ ciftciIntervalHandle = null;
211
+ logger.info("[Çiftçi Periyodik] Döngü zamanlayıcısı durduruldu.");
212
+ }
213
+
214
+ nextScheduledCheckAtMs = null;
215
+ nextKaziciAtMs = null;
216
+ nextCiftciAtMs = null;
217
+ nextKaziciTimeStr = "Zamanlama durduruldu.";
218
+ nextCiftciTimeStr = "Zamanlama durduruldu.";
219
+ }
220
+
221
+ // --- MANUEL DURDURMA/BAŞLATMA KONTROLÜ ---
222
+ let manualStopRequested = false;
223
 
224
  async function getIpLocation(ip) {
225
  if (!ip || ip === "Bilinmiyor" || ip.startsWith("127.")) return "Bilinmiyor";
226
 
 
227
  const services = [
228
  `https://ip-api.com/json/${ip}?fields=status,country,city`,
229
  `https://ipapi.co/${ip}/json/`
 
238
  res.on('end', () => {
239
  try {
240
  const geo = JSON.parse(data);
 
241
  if (geo.country && geo.status !== "fail") {
242
  resolve(`${geo.country} / ${geo.city || geo.region || ""}`);
243
  } else {
244
+ resolve(null);
245
  }
246
  } catch (e) { resolve(null); }
247
  });
 
272
  logger.warn("[Sistem] Zaten aktif bir bağlantı süreci yürütülüyor. Mükerrer istek engellendi.");
273
  return;
274
  }
275
+ isConnecting = true;
276
+ isDestroying = false;
277
+
278
+ try {
279
 
280
  const net = require('net');
281
  const sshClient = new Client();
282
+ currentSshClient = sshClient;
283
 
284
  const targetServerIp = config.server.ip;
285
  const targetServerPort = config.server.port;
 
297
  let isFinalFailureHandled = false;
298
 
299
  const handleFinalFailure = (reason) => {
300
+ if (isFinalFailureHandled || isDestroying) return;
301
  isFinalFailureHandled = true;
302
 
303
  logger.error(`[Kritik Hata] ${reason}. Bağlantı sağlanamadı! Sistem en başa döndürülüyor...`);
304
+
305
  baglantiTuru = "Yok";
306
  botIsReady = false;
307
+ botUptimeStart = null;
308
  activeConnectionIp = "Bilinmiyor";
309
  activeConnectionLocation = "Bilinmiyor";
310
 
311
  stopPeriodicHomeChecks();
312
+ lastCheckAtMs = null;
313
+ safeDestroyAllConnections();
314
 
315
+ if (!manualStopRequested && config.utils && config.utils["auto-reconnect"]) {
316
  triggerSafeReconnect();
317
  }
318
  };
319
 
320
  const connectDirectly = async (reason) => {
321
+ if (fallbackTriggered || isDestroying) return;
322
  fallbackTriggered = true;
323
 
324
  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...`);
 
327
  try {
328
  sshClient.removeAllListeners('ready');
329
  sshClient.removeAllListeners('error');
330
+ sshClient.destroy();
331
+ } catch (e) {}
332
  }
333
 
334
  activeConnectionIp = localExternalIp !== "Bilinmiyor" ? localExternalIp : "Kendi Dış IP'niz";
 
338
  host: targetServerIp,
339
  port: parseInt(targetServerPort) || 25565
340
  });
341
+ currentSocket = socket;
342
 
343
  let isDirectHandled = false;
344
 
345
  const directTimeout = setTimeout(() => {
346
  if (isDirectHandled) return;
347
  isDirectHandled = true;
348
+
349
  socket.destroy();
350
  handleFinalFailure("Kendi IP bağlantısı 1 dakikalık süre içinde zaman aşımına uğradı (Sunucu yanıt vermedi)");
351
  }, 60000);
 
353
  socket.on('connect', () => {
354
  if (isDirectHandled) return;
355
  isDirectHandled = true;
356
+
357
  clearTimeout(directTimeout);
358
  logger.info("[Bağlantı Yedekleme] Kendi IP'miz üzerinden GERÇEK TCP bağlantısı başarıyla kuruldu!");
359
  baglantiTuru = "Normal";
 
371
  logger.error(`[Bağlantı Yedekleme Hatası] Sunucu bağlantıyı reddetti veya olumsuz yanıt verdi: ${err.message}`);
372
  handleFinalFailure(`Kendi IP hatası: ${err.message}`);
373
  });
374
+
375
  socket.on('close', () => {
376
  if (isDirectHandled) return;
377
  isDirectHandled = true;
 
381
  });
382
  };
383
 
384
+ try {
385
  const githubRawUrl = 'https://raw.githubusercontent.com/suvarileader/che/refs/heads/main/doc/ower/doc.json';
386
  logger.info("[GitHub Config] Güncel SSH tünel bilgileri GitHub'dan çekiliyor...");
387
 
 
446
  logger.info('[SSH Tünel] SSH bağlantısı başarılı. Minecraft sunucusuna tünel açılıyor...');
447
 
448
  try {
449
+ try {
450
+ const lookup = await dns.lookup(sshConfig.host);
451
+ activeConnectionIp = lookup.address;
452
+ } catch (e) {
453
+ activeConnectionIp = sshConfig.host;
454
+ }
455
+ activeConnectionLocation = await getIpLocation(activeConnectionIp);
456
+
457
+ let tunnelHandled = false;
458
+ const tunnelTimeout = setTimeout(() => {
459
+ if (tunnelHandled || isDestroying) return;
460
+ tunnelHandled = true;
461
+ connectDirectly("SSH tüneli (forwardOut) 10 saniye içinde açılamadı (sunucu/ağ lagı olabilir)");
462
+ }, 10000);
463
+
464
+ sshClient.forwardOut(
465
+ '127.0.0.1',
466
+ 12345,
467
+ targetServerIp,
468
+ parseInt(targetServerPort) || 25565,
469
+ (err, stream) => {
470
+ if (tunnelHandled || isDestroying) return;
471
+ tunnelHandled = true;
472
+ clearTimeout(tunnelTimeout);
473
+
474
+ if (err) {
475
+ connectDirectly(`Tünel yönlendirme hatası: ${err.message}`);
476
+ return;
 
 
 
477
  }
 
478
 
479
+ logger.info("[SSH Tünel] Tünel başarıyla kuruldu! Mineflayer sokete bağlanıyor.");
480
+ baglantiTuru = "SSH";
481
+ currentStream = stream;
482
+
483
+ stream.once('close', () => {
484
+ if (!fallbackTriggered && !isDestroying) {
485
+ connectDirectly("Tünel stream kapandı");
486
+ }
487
+ });
488
+
489
+ stream.once('error', (e) => {
490
+ if (!fallbackTriggered && !isDestroying) {
491
+ connectDirectly(`Stream hatası: ${e.message}`);
492
+ }
493
+ });
494
+
495
+ if (client && typeof client.setSocket === 'function') {
496
+ client.setSocket(stream);
497
+ client.emit('connect');
498
+ }
499
  }
500
+ );
501
+ } catch (err) {
502
+ if (!isDestroying) {
503
+ connectDirectly(`SSH 'ready' işleyicisinde beklenmeyen hata: ${err && err.message ? err.message : err}`);
504
  }
505
+ }
506
  });
507
 
508
  sshClient.on('error', (err) => {
 
518
  clearTimeout(connectionTimeout);
519
  connectDirectly(`Sistem Hatası: ${err.message}`);
520
  }
521
+ } catch (err) {
522
+ logger.error(`[connect() Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`);
523
+ if (!isDestroying) {
524
+ handleFinalFailure(`connect() içinde yakalanmayan hata: ${err && err.message ? err.message : err}`);
525
+ }
526
+ }
527
  }
528
  });
529
 
 
530
 
531
  let lastFullFeedAt = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
 
533
  function getExtendedDateTimeString() {
534
  const now = new Date();
 
569
  if (!s || typeof s !== "string") return "";
570
  let t = stripColorCodes(s);
571
  if (t.normalize) t = t.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
572
+ try {
573
+ return t.toLocaleLowerCase('tr-TR');
574
+ } catch (e) {
575
+ return t.toLowerCase();
576
+ }
577
+ }
578
+
579
+ function normalizeVariants(s) {
580
+ if (!s || typeof s !== "string") return [""];
581
+ let t = stripColorCodes(s);
582
+ if (t.normalize) t = t.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
583
+ const variants = new Set();
584
+ try { variants.add(t.toLocaleLowerCase('tr-TR')); } catch (e) {}
585
+ variants.add(t.toLowerCase());
586
+ return Array.from(variants);
587
+ }
588
+
589
+ function matchesAnyName(sourceString, targetVariantsList) {
590
+ if (!sourceString) return false;
591
+ const sourceVariants = normalizeVariants(sourceString);
592
+ return sourceVariants.some(sv => targetVariantsList.some(t => sv.includes(t)));
593
  }
594
 
595
+ const TARGET_NAME_NORMS = BESLEYICI_NAMES.flatMap(n => normalizeVariants(n));
596
 
597
  function isBesleyiciName(name) {
598
+ return matchesAnyName(name, TARGET_NAME_NORMS);
599
+ }
600
+
601
+ const TARGET_KAZICI_NORMS = KAZICI_NAMES.flatMap(n => normalizeVariants(n));
602
+
603
+ function isKaziciName(name) {
604
+ return matchesAnyName(name, TARGET_KAZICI_NORMS);
605
+ }
606
+
607
+ const TARGET_CIFTCI_NORMS = CIFTCI_NAMES.flatMap(n => normalizeVariants(n));
608
+
609
+ function isCiftciName(name) {
610
+ return matchesAnyName(name, TARGET_CIFTCI_NORMS);
611
  }
612
 
613
  function safeGet(obj, pathArray) {
 
621
  } catch (e) { return undefined; }
622
  }
623
 
624
+ function deepSearchForString(root, targetVariantsList) {
625
  const results = [];
626
  const visited = new WeakSet();
627
  function recurse(obj, path) {
628
  if (obj == null || typeof obj === "function") return;
629
  if (typeof obj === "string") {
630
+ if (matchesAnyName(obj, targetVariantsList)) results.push({ path, value: obj });
 
631
  return;
632
  }
633
  if (typeof obj !== "object") return;
 
689
  } catch (err) { return null; }
690
  }
691
 
692
+ function getItemDisplayTexts(slot) {
693
+ const texts = [];
694
+ try {
695
+ if (!slot) return texts;
696
+
697
+ function extractPlain(raw) {
698
+ if (!raw || typeof raw !== "string") return null;
699
+ try {
700
+ const maybe = JSON.parse(raw);
701
+ if (maybe && typeof maybe === "object") {
702
+ if (maybe.extra && Array.isArray(maybe.extra)) {
703
+ return maybe.extra.map(x => (x && typeof x.text === "string") ? x.text : "").join("");
704
+ } else if (typeof maybe.text === "string") {
705
+ return maybe.text;
706
+ }
707
+ }
708
+ } catch (e) {}
709
+ return raw;
710
+ }
711
+
712
+ if (slot.nbt) {
713
+ const name = safeGet(slot.nbt, ["value", "display", "value", "Name", "value"]);
714
+ const plainName = extractPlain(name);
715
+ if (plainName) texts.push(plainName);
716
+
717
+ const lore = safeGet(slot.nbt, ["value", "display", "value", "Lore", "value"]);
718
+ if (Array.isArray(lore)) {
719
+ for (const line of lore) {
720
+ const plainLine = extractPlain(line.value || line);
721
+ if (plainLine) texts.push(plainLine);
722
+ }
723
+ }
724
+ }
725
+
726
+ if (texts.length === 0) {
727
+ texts.push(util.inspect(slot, { depth: null }));
728
+ }
729
+ } catch (err) {}
730
+ return texts;
731
+ }
732
+
733
+ function windowMatchesLoreKeyword(window, keywords) {
734
+ try {
735
+ if (!window || !Array.isArray(window.slots)) return false;
736
+ const normKeywords = keywords.flatMap(k => normalizeVariants(k));
737
+ for (const slot of window.slots) {
738
+ if (!slot) continue;
739
+ const texts = getItemDisplayTexts(slot);
740
+ for (const t of texts) {
741
+ if (matchesAnyName(t, normKeywords)) return true;
742
+ }
743
+ }
744
+ } catch (e) {}
745
+ return false;
746
+ }
747
+
748
+ async function goToIsland() {
749
  if (!bot || !botIsReady) return;
750
+ try {
751
+ logger.info(`[${currentBotUsername}] ${GO_ISLAND_COMMAND} komutu gönderiliyor.`);
752
+ bot.chat(GO_ISLAND_COMMAND);
753
+ } catch (err) {
754
+ logger.error(`[goToIsland Hatası] Komut gönderilemedi: ${err && err.message ? err.message : err}`);
755
+ }
756
  }
757
 
758
+ async function sendSkyblockCommand() {
759
  if (!bot || !botIsReady) return;
760
+ try {
761
+ logger.info(`[${currentBotUsername}] /skyblock komutu (manuel) gönderiliyor.`);
762
+ bot.chat("/skyblock");
763
+ } catch (err) {
764
+ logger.error(`[sendSkyblockCommand Hatası] Komut gönderilemedi: ${err && err.message ? err.message : err}`);
765
+ }
766
  }
767
 
768
+ // --- DİNAMİK PLANLAMA MOTORU (SADECE BESLEYİCİ İÇİN) ---
769
  function scheduleNextCheck(delayMs) {
770
  if (periodicTimeoutHandle) {
771
  clearTimeout(periodicTimeoutHandle);
772
  }
773
+
774
+ nextScheduledCheckAtMs = Date.now() + delayMs;
775
+ nextScheduledCheckTimeStr = `${getFutureDateTimeString(delayMs)} (${formatDurationMs(delayMs)} sonra)`;
776
+ logger.info(`[Planlayıcı] Bir sonraki besleyici kontrolü planlandı. Zaman: ${nextScheduledCheckTimeStr}`);
777
 
778
  periodicTimeoutHandle = setTimeout(async () => {
779
  if (!bot || !botIsReady) return;
780
+ isMainCycleRunning = true;
781
+ try {
782
+ await goToIsland();
783
+ await new Promise(r => setTimeout(r, HOME_ARRIVAL_DELAY_MS));
784
+ await scanAndFeed();
785
+ // NOT: Kazıcı ve çiftçi toplama artık BURADA çağrılmıyor. Her ikisi de
786
+ // besleyiciden tamamen bağımsız kendi zamanlayıcılarında çalışır
787
+ // (bkz. startKaziciPeriodic / startCiftciPeriodic).
788
+ } catch (err) {
789
+ logger.error(`[Planlayıcı Hatası] Periyodik döngüde beklenmeyen hata: ${err && err.stack ? err.stack : err}`);
790
+ try { scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } catch (e2) {}
791
+ } finally {
792
+ isMainCycleRunning = false;
793
+ }
794
  }, delayMs);
795
  }
796
 
797
+ function activateEntityAtNoLook(entity, position) {
798
+ bot._client.write('use_entity', {
799
+ target: entity.id,
800
+ mouse: 2,
801
+ sneaking: false,
802
+ hand: 0,
803
+ x: position.x - entity.position.x,
804
+ y: position.y - entity.position.y,
805
+ z: position.z - entity.position.z
806
+ });
807
+ }
808
+
809
+ // --- BESLEYİCİ (scanAndFeedCore = asıl mantık, kilit içermez) ---
810
+ async function scanAndFeedCore() {
811
  if (!bot || !botIsReady) return;
812
+ if (!guiMutex.isLocked()) {
813
+ logger.error("[GÜVENLİK İHLALİ] scanAndFeedCore kilitsiz çağrıldı! Çakışma riski var, işlem iptal ediliyor.");
814
+ return;
815
+ }
816
+
817
  if (Date.now() < (lastFullFeedAt + 30000)) {
818
+ logger.warn("[Besleyici] Çok kısa süre önce işlem yapıldı. Güvenlik kilidi devrede.");
819
+ return;
820
+ }
821
+
822
+ if (!bot.entities || !bot.entity || !bot.entity.position) {
823
+ logger.warn("[Besleyici] Bot durumu (entities/position) henüz hazır değil, muhtemelen lag veya senkronizasyon gecikmesi. 10 dk sonra tekrar denenecek.");
824
+ scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
825
  return;
826
  }
827
 
 
834
  best = { entity: e, path: "displayName/name" };
835
  break;
836
  }
837
+ const deep = deepSearchForString(e, TARGET_NAME_NORMS);
838
+ if (deep.length > 0) {
839
+ best = { entity: e, path: deep[0].path };
840
+ break;
 
 
841
  }
 
842
  }
843
 
844
  if (!best) {
845
  logger.info(`[${currentBotUsername}] /home sonrası tarama: besleyici minyon bulunamadı.`);
846
  bot.chat("/skyblock");
847
+ scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
848
+ return;
849
+ }
850
+
851
+ if (!best.entity.position) {
852
+ logger.warn(`[${currentBotUsername}] Bulunan entity'nin pozisyon verisi yok (muhtemelen lag). 10 dk sonra tekrar denenecek.`);
853
  scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
854
  return;
855
  }
 
864
  return;
865
  }
866
 
867
+ await new Promise(r => setTimeout(r, 400));
868
+
869
+ if (bot.currentWindow != null) {
870
+ logger.warn(`[${currentBotUsername}] Beklenmedik şekilde eski bir pencere referansı hâlâ duruyor, kapatılıp temizleniyor...`);
871
+ try { bot.closeWindow(bot.currentWindow); } catch (e) {}
872
+ await new Promise(r => setTimeout(r, 300));
873
+ }
874
+
875
+ const startTime = Date.now();
876
+ const endTime = startTime + WINDOW_OPEN_TIMEOUT_MS;
877
+ let clickCount = 0;
878
+ let isLoopActive = true;
879
+
880
+ await new Promise((resolve) => {
881
+ let clickInterval = null;
882
+ const cleanFinish = () => {
883
+ isLoopActive = false;
884
+ clearInterval(clickInterval);
885
+ windowOpenResolvers = [];
886
+ resolve();
887
+ };
888
+
889
+ windowOpenResolvers.push(() => {
890
+ cleanFinish();
891
+ });
892
+
893
+ const sendClickPacket = () => {
894
+ if (!isLoopActive) return;
895
+
896
+ if (!bot || !botIsReady) {
897
+ logger.warn("[Tıklama Sistemi] Bot artık hazır değil (bağlantı koptu?). Döngü iptal ediliyor.");
898
+ cleanFinish();
899
+ return;
900
+ }
901
+
902
+ if (Date.now() > endTime) {
903
+ logger.warn(`[Tıklama Sistemi] ${WINDOW_OPEN_TIMEOUT_MS} ms doldu ancak menü sunucudan gelmedi. Pes ediliyor.`);
904
+ cleanFinish();
905
+ scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
906
+ return;
907
+ }
908
+
909
+ clickCount++;
910
+ logger.info(`[Tıklama Sistemi] Menü tespiti başarısız. Deneme #${clickCount} gönderiliyor... (id:${best.entity.id})`);
911
+
912
+ try {
913
+ activateEntityAtNoLook(best.entity, best.entity.position);
914
+ } catch (err) {}
915
+ };
916
+
917
+ sendClickPacket();
918
+ clickInterval = setInterval(sendClickPacket, 600);
919
+ });
920
+ }
921
+
922
+ // Kilit meşgulken (kazıcı/çiftçi turu sürerken) besleyicinin bu kısa süreliğine
923
+ // beklemesi için yeniden deneme aralığı. Gerçek besleme zamanlamasını bozmaz,
924
+ // sadece kilidin boşalmasını birkaç kez kontrol eder.
925
+ const FEED_LOCK_RETRY_DELAY_MS = 15 * 1000;
926
+ const FEED_LOCK_MAX_RETRIES = 8; // 8 x 15sn = 2 dk boyunca dener
927
+
928
+ // Periyodik (otomatik) kullanım: kilit doluysa hemen pes etmez, kısa aralıklarla
929
+ // yeniden dener (kazıcı/çiftçi turu birkaç saniye içinde biter). Sonuçta kilidi
930
+ // hâlâ alamazsa, döngünün tamamen ölmemesi için normal aralıkla tekrar planlar.
931
+ async function scanAndFeed(retryCount) {
932
+ retryCount = retryCount || 0;
933
+ if (!guiMutex.tryAcquire()) {
934
+ if (retryCount < FEED_LOCK_MAX_RETRIES) {
935
+ 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}).`);
936
+ setSafeTimeout(() => { scanAndFeed(retryCount + 1); }, FEED_LOCK_RETRY_DELAY_MS);
937
+ } else {
938
+ 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.");
939
+ try { scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } catch (e2) {}
940
+ }
941
+ return;
942
+ }
943
  try {
944
+ await scanAndFeedCore();
945
+ } catch (err) {
946
+ logger.error(`[scanAndFeed Hatası] Beklenmeyen bir hata oluştu, döngü güvenle durduruldu: ${err && err.stack ? err.stack : err}`);
947
+ try { scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } catch (e2) {}
948
+ } finally {
949
+ guiMutex.release();
950
+ }
951
+ }
952
+
953
+ // Manuel (Express panelinden) kullanım: kilit doluysa ÖNCELİKLİ olarak kuyruğa
954
+ // girer, mevcut işlem bitince hemen devralır.
955
+ async function scanAndFeedManual() {
956
+ logger.info("[Besleyici - Manuel] Panelden istek geldi, sırasını bekliyor (öncelikli)...");
957
+ await guiMutex.acquire();
958
+ try {
959
+ logger.info("[Besleyici - Manuel] Kilit alındı, kontrol başlıyor.");
960
+ await scanAndFeedCore();
961
+ } catch (err) {
962
+ logger.error(`[scanAndFeedManual Hatası] ${err && err.stack ? err.stack : err}`);
963
+ } finally {
964
+ guiMutex.release();
965
+ }
966
+ }
967
+
968
+ // --- KAZICI MİNYON DEPO AKTARIMI ---
969
+ async function activateEntityAndAwaitWindow(entity, labelForLogs) {
970
+ if (!bot || !botIsReady || !entity || !entity.position) {
971
+ return { success: false, reason: "Bot veya entity hazır değil" };
972
+ }
973
+ if (!bot.entity || !bot.entity.position) {
974
+ return { success: false, reason: "Bot pozisyon verisi henüz hazır değil" };
975
+ }
976
+
977
+ const dist = bot.entity.position.distanceTo(entity.position);
978
+ if (dist > 7) {
979
+ return { success: false, reason: `Çok uzak (${dist.toFixed(2)} blok, max 7)` };
980
+ }
981
+
982
+ await new Promise(r => setTimeout(r, 400));
983
+
984
+ if (bot.currentWindow != null) {
985
+ logger.warn(`[Tıklama Sistemi - ${labelForLogs}] Beklenmedik şekilde eski bir pencere referansı hâlâ duruyor, kapatılıp temizleniyor...`);
986
+ try { bot.closeWindow(bot.currentWindow); } catch (e) {}
987
+ await new Promise(r => setTimeout(r, 300));
988
+ }
989
+
990
+ const startTime = Date.now();
991
+ const endTime = startTime + WINDOW_OPEN_TIMEOUT_MS;
992
+ let clickCount = 0;
993
+ let isLoopActive = true;
994
+ let timedOut = false;
995
+
996
+ await new Promise((resolve) => {
997
+ let clickInterval = null;
998
+ const cleanFinish = () => {
999
+ isLoopActive = false;
1000
+ clearInterval(clickInterval);
1001
+ windowOpenResolvers = [];
1002
+ resolve();
1003
+ };
1004
+
1005
+ windowOpenResolvers.push(() => {
1006
+ cleanFinish();
1007
+ });
1008
+
1009
+ const sendClickPacket = () => {
1010
+ if (!isLoopActive) return;
1011
+
1012
+ if (!bot || !botIsReady) {
1013
+ logger.warn(`[Tıklama Sistemi - ${labelForLogs}] Bot artık hazır değil. Döngü iptal ediliyor.`);
1014
  cleanFinish();
1015
+ return;
1016
+ }
1017
 
1018
+ if (Date.now() > endTime) {
1019
+ logger.warn(`[Tıklama Sistemi - ${labelForLogs}] ${WINDOW_OPEN_TIMEOUT_MS} ms doldu ancak menü sunucudan gelmedi. Pes ediliyor.`);
1020
+ timedOut = true;
1021
+ cleanFinish();
1022
+ return;
1023
+ }
1024
 
1025
+ clickCount++;
1026
+ logger.info(`[Tıklama Sistemi - ${labelForLogs}] Menü tespiti başarısız. Deneme #${clickCount} gönderiliyor... (id:${entity.id})`);
 
 
1027
 
1028
+ try {
1029
+ activateEntityAtNoLook(entity, entity.position);
1030
+ } catch (err) {}
1031
+ };
 
 
1032
 
1033
+ sendClickPacket();
1034
+ clickInterval = setInterval(sendClickPacket, 600);
1035
+ });
1036
+
1037
+ if (timedOut) {
1038
+ return { success: false, reason: "Pencere zaman aşımına uğradı" };
1039
+ }
1040
+ return { success: true };
1041
+ }
1042
+
1043
+ function findAllKaziciEntities() {
1044
+ const arr = Object.values(bot.entities);
1045
+ const matches = [];
1046
+ const seenIds = new Set();
1047
+
1048
+ for (const e of arr) {
1049
+ const name = getEntityReadableName(e);
1050
+ let matched = isKaziciName(name);
1051
+
1052
+ if (!matched) {
1053
+ const deep = deepSearchForString(e, TARGET_KAZICI_NORMS);
1054
+ matched = deep.length > 0;
1055
+ }
1056
+
1057
+ if (matched && e && e.id != null && !seenIds.has(e.id)) {
1058
+ seenIds.add(e.id);
1059
+ matches.push(e);
1060
+ }
1061
+ }
1062
+
1063
+ return matches;
1064
+ }
1065
+
1066
+ // scanAndCollectMinersCore: asıl mantık, kilit İÇERMEZ (dışarıdan kilitlenmiş
1067
+ // olarak çağrılması beklenir).
1068
+ async function scanAndCollectMinersCore() {
1069
+ if (!bot || !botIsReady) return;
1070
+ if (!guiMutex.isLocked()) {
1071
+ logger.error("[GÜVENLİK İHLALİ] scanAndCollectMinersCore kilitsiz çağrıldı! Çakışma riski var, işlem iptal ediliyor.");
1072
+ return;
1073
+ }
1074
+
1075
+ if (!bot.entities || !bot.entity || !bot.entity.position) {
1076
+ logger.warn("[Kazıcı Toplama] Bot durumu (entities/position) henüz hazır değil, muhtemelen lag. Bu tur atlanıyor.");
1077
+ return;
1078
+ }
1079
+
1080
+ const miners = findAllKaziciEntities();
1081
+
1082
+ if (miners.length === 0) {
1083
+ logger.info(`[${currentBotUsername}] Kazıcı minyon bulunamadı, depo aktarım turu atlandı.`);
1084
+ return;
1085
+ }
1086
+
1087
+ logger.info(`[${currentBotUsername}] ${miners.length} adet kazıcı minyon bulundu. Sırayla depo aktarımı yapılacak (aralarda ${formatDurationMs(KAZICI_BETWEEN_DELAY_MS)} bekleme).`);
1088
+
1089
+ for (let i = 0; i < miners.length; i++) {
1090
+ if (!bot || !botIsReady) {
1091
+ logger.warn("[Kazıcı Toplama] Bot artık hazır değil, tur erken sonlandırılıyor.");
1092
+ break;
1093
+ }
1094
+
1095
+ const entity = miners[i];
1096
+ const label = `Kazıcı #${i + 1}/${miners.length}`;
1097
+
1098
+ if (!entity || !entity.position) {
1099
+ logger.warn(`[Kazıcı Toplama] ${label} pozisyon verisi kayboldu (lag olabilir), atlanıyor.`);
1100
+ } else {
1101
+ logger.info(`[${currentBotUsername}] ${label} (id:${entity.id}) aktive ediliyor...`);
1102
+ const result = await activateEntityAndAwaitWindow(entity, label);
1103
+ if (!result.success) {
1104
+ logger.warn(`[Kazıcı Toplama] ${label} başarısız: ${result.reason}`);
1105
+ } else {
1106
+ logger.info(`[Kazıcı Toplama] ${label} depo aktarım işlemi tamamlandı.`);
1107
+ }
1108
+ }
1109
+
1110
+ if (i < miners.length - 1) {
1111
+ await new Promise(r => setTimeout(r, KAZICI_BETWEEN_DELAY_MS));
1112
+ }
1113
+ }
1114
+
1115
+ logger.info(`[${currentBotUsername}] Kazıcı minyon depo aktarım turu tamamlandı.`);
1116
+ }
1117
+
1118
+ // Tekil/periyodik kullanım (kendi başına çağrılırsa kilidi kendisi alır)
1119
+ async function scanAndCollectMiners() {
1120
+ if (!guiMutex.tryAcquire()) {
1121
+ logger.warn("[Kazıcı Toplama] Zaten bir GUI işlemi sürüyor (besleyici/kazıcı/çiftçi). Bu tur atlanıyor.");
1122
+ return;
1123
+ }
1124
+ try {
1125
+ await scanAndCollectMinersCore();
1126
+ } catch (err) {
1127
+ logger.error(`[scanAndCollectMiners Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`);
1128
+ } finally {
1129
+ guiMutex.release();
1130
+ }
1131
+ }
1132
+
1133
+ // Manuel (Express panelinden) kullanım: öncelikli kuyruk.
1134
+ async function scanAndCollectMinersManual() {
1135
+ logger.info("[Kazıcı Toplama - Manuel] Panelden istek geldi, sırasını bekliyor (öncelikli)...");
1136
+ await guiMutex.acquire();
1137
+ try {
1138
+ logger.info("[Kazıcı Toplama - Manuel] Kilit alındı, tur başlıyor.");
1139
+ await scanAndCollectMinersCore();
1140
+ lastKaziciCheckTimeStr = getExtendedDateTimeString();
1141
+ } catch (err) {
1142
+ logger.error(`[scanAndCollectMinersManual Hatası] ${err && err.stack ? err.stack : err}`);
1143
+ } finally {
1144
+ guiMutex.release();
1145
+ }
1146
+ }
1147
+
1148
+ // --- ÇİFTÇİ (HEPSİNİ SAT) MENÜSÜ ---
1149
+ // openCiftciAndSellAllCore: asıl mantık, kilit İÇERMEZ (dışarıdan kilitli çağrılır).
1150
+ // NOT: Asıl "hepsini sat" tıklaması artık burada DEĞİL, windowOpen olayında,
1151
+ // pencere açıldıktan CIFTCI_CLICK_DELAY_AFTER_OPEN_MS kadar bekledikten sonra
1152
+ // ve CIFTCI_CLICK_REPEAT_COUNT kez tekrar tıklanarak yapılıyor (bkz. windowOpen).
1153
+ async function openCiftciAndSellAllCore() {
1154
+ if (!bot || !botIsReady) return;
1155
+ if (!guiMutex.isLocked()) {
1156
+ logger.error("[GÜVENLİK İHLALİ] openCiftciAndSellAllCore kilitsiz çağrıldı! Çakışma riski var, işlem iptal ediliyor.");
1157
+ return;
1158
+ }
1159
 
1160
+ logger.info(`[${currentBotUsername}] Çiftçi (Hepsini Sat) menüsü açılıyor (${CIFTCI_COMMAND})...`);
1161
+
1162
+ if (bot.currentWindow != null) {
1163
+ logger.warn(`[${currentBotUsername}] [Çiftçi] Beklenmedik şekilde eski bir pencere referansı hâlâ duruyor, kapatılıp temizleniyor...`);
1164
+ try { bot.closeWindow(bot.currentWindow); } catch (e) {}
1165
+ await new Promise(r => setTimeout(r, 300));
1166
+ }
1167
+
1168
+ // Komut yalnızca BİR KEZ gönderilir. Eskiden birkaç saniyede bir tekrar
1169
+ // gönderiliyordu; bu, sunucunun aynı komutu art arda işleyip menüyü birden
1170
+ // fazla kez açmasına (ve dolayısıyla tıklama/satış tutarsızlıklarına) yol
1171
+ // açabiliyordu. Şimdi sadece pencerenin açılmasını timeout süresince bekliyoruz.
1172
+ let timedOut = false;
1173
+
1174
+ await new Promise((resolve) => {
1175
+ let timeoutHandle = null;
1176
+ const cleanFinish = () => {
1177
+ clearTimeout(timeoutHandle);
1178
+ windowOpenResolvers = [];
1179
+ resolve();
1180
+ };
1181
+
1182
+ windowOpenResolvers.push(() => {
1183
+ cleanFinish();
1184
  });
1185
 
1186
+ timeoutHandle = setTimeout(() => {
1187
+ logger.warn(`[Çiftçi] ${formatDurationMs(CIFTCI_WINDOW_TIMEOUT_MS)} doldu ancak menü sunucudan gelmedi. Pes ediliyor.`);
1188
+ timedOut = true;
1189
+ cleanFinish();
1190
+ }, CIFTCI_WINDOW_TIMEOUT_MS);
1191
+
1192
+ if (!bot || !botIsReady) {
1193
+ logger.warn("[Çiftçi] Bot artık hazır değil. İşlem iptal ediliyor.");
1194
+ timedOut = true;
1195
+ cleanFinish();
1196
+ return;
1197
+ }
1198
+
1199
+ logger.info(`[Çiftçi] Menü komutu gönderiliyor (tek seferlik)...`);
1200
+ try { bot.chat(CIFTCI_COMMAND); } catch (e) {}
1201
+ });
1202
+
1203
+ if (timedOut) {
1204
+ logger.warn("[Çiftçi] Menü açılamadı, bu tur atlanıyor.");
1205
+ } else {
1206
+ logger.info(`[${currentBotUsername}] Çiftçi menü turu tamamlandı.`);
1207
+ }
1208
+ }
1209
+
1210
+ // Tekil/periyodik kullanım.
1211
+ async function openCiftciAndSellAll() {
1212
+ if (!guiMutex.tryAcquire()) {
1213
+ logger.warn("[Çiftçi] Zaten bir GUI işlemi sürüyor (besleyici/kazıcı/çiftçi). Bu tur atlanıyor.");
1214
+ return;
1215
+ }
1216
+ try {
1217
+ await openCiftciAndSellAllCore();
1218
+ } catch (err) {
1219
+ logger.error(`[openCiftciAndSellAll Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`);
1220
+ } finally {
1221
+ guiMutex.release();
1222
+ }
1223
+ }
1224
+
1225
+ // Manuel (Express panelinden) kullanım: öncelikli kuyruk.
1226
+ async function openCiftciAndSellAllManual() {
1227
+ logger.info("[Çiftçi - Manuel] Panelden istek geldi, sırasını bekliyor (öncelikli)...");
1228
+ await guiMutex.acquire();
1229
+ try {
1230
+ logger.info("[Çiftçi - Manuel] Kilit alındı, satış başlıyor.");
1231
+ await openCiftciAndSellAllCore();
1232
+ lastCiftciSellTimeStr = getExtendedDateTimeString();
1233
+ } catch (err) {
1234
+ logger.error(`[openCiftciAndSellAllManual Hatası] ${err && err.stack ? err.stack : err}`);
1235
+ } finally {
1236
+ guiMutex.release();
1237
+ }
1238
+ }
1239
+
1240
+ // --- KAZICI BAĞIMSIZ PERİYODİK DÖNGÜ (5 dakikada bir) ---
1241
+ // Besleyici ve çiftçi durumundan hiçbir şekilde etkilenmez. Sadece paylaşılan
1242
+ // guiMutex ile çakışmayı önler; kilit doluysa bu turu sessizce atlar.
1243
+ function startKaziciPeriodic() {
1244
+ if (kaziciIntervalHandle) return; // zaten çalışıyor
1245
+
1246
+ const runCycle = async () => {
1247
+ if (!bot || !botIsReady) return;
1248
+ if (manualStopRequested || isDestroying) return;
1249
+
1250
+ if (!guiMutex.tryAcquire()) {
1251
+ logger.info("[Kazıcı Periyodik] Başka bir GUI işlemi sürüyor, bu tur atlanıyor.");
1252
+ return;
1253
+ }
1254
+
1255
+ try {
1256
+ logger.info("[Kazıcı Periyodik] Tur başlıyor: kazıcı depo aktarımı.");
1257
+ await scanAndCollectMinersCore();
1258
+ lastKaziciCheckTimeStr = getExtendedDateTimeString();
1259
+ logger.info(`[Kazıcı Periyodik] Tur tamamlandı. ${formatDurationMs(KAZICI_CYCLE_INTERVAL_MS)} sonra tekrar denenecek.`);
1260
+ } catch (err) {
1261
+ logger.error(`[Kazıcı Periyodik Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`);
1262
+ } finally {
1263
+ guiMutex.release();
1264
+ nextKaziciAtMs = Date.now() + KAZICI_CYCLE_INTERVAL_MS;
1265
+ nextKaziciTimeStr = `${getFutureDateTimeString(KAZICI_CYCLE_INTERVAL_MS)} (${formatDurationMs(KAZICI_CYCLE_INTERVAL_MS)} sonra)`;
1266
+ }
1267
+ };
1268
+
1269
+ kaziciIntervalHandle = setInterval(runCycle, KAZICI_CYCLE_INTERVAL_MS);
1270
+ nextKaziciAtMs = Date.now() + KAZICI_CYCLE_INTERVAL_MS;
1271
+ nextKaziciTimeStr = `${getFutureDateTimeString(KAZICI_CYCLE_INTERVAL_MS)} (${formatDurationMs(KAZICI_CYCLE_INTERVAL_MS)} sonra)`;
1272
+ logger.info(`[Kazıcı Periyodik] Bağımsız döngü başlatıldı (her ${formatDurationMs(KAZICI_CYCLE_INTERVAL_MS)}).`);
1273
+ }
1274
+
1275
+ // --- ÇİFTÇİ BAĞIMSIZ PERİYODİK DÖNGÜ (1 dakikada bir) ---
1276
+ function startCiftciPeriodic() {
1277
+ if (ciftciIntervalHandle) return; // zaten çalışıyor
1278
+
1279
+ const runCycle = async () => {
1280
+ if (!bot || !botIsReady) return;
1281
+ if (manualStopRequested || isDestroying) return;
1282
+
1283
+ if (!guiMutex.tryAcquire()) {
1284
+ logger.info("[Çiftçi Periyodik] Başka bir GUI işlemi sürüyor, bu tur atlanıyor.");
1285
+ return;
1286
+ }
1287
+
1288
+ try {
1289
+ logger.info("[Çiftçi Periyodik] Tur başlıyor: çiftçi hepsini-sat.");
1290
+ await openCiftciAndSellAllCore();
1291
+ lastCiftciSellTimeStr = getExtendedDateTimeString();
1292
+ logger.info(`[Çiftçi Periyodik] Tur tamamlandı. ${formatDurationMs(CIFTCI_CYCLE_INTERVAL_MS)} sonra tekrar denenecek.`);
1293
+ } catch (err) {
1294
+ logger.error(`[Çiftçi Periyodik Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`);
1295
+ } finally {
1296
+ guiMutex.release();
1297
+ nextCiftciAtMs = Date.now() + CIFTCI_CYCLE_INTERVAL_MS;
1298
+ nextCiftciTimeStr = `${getFutureDateTimeString(CIFTCI_CYCLE_INTERVAL_MS)} (${formatDurationMs(CIFTCI_CYCLE_INTERVAL_MS)} sonra)`;
1299
+ }
1300
+ };
1301
+
1302
+ ciftciIntervalHandle = setInterval(runCycle, CIFTCI_CYCLE_INTERVAL_MS);
1303
+ nextCiftciAtMs = Date.now() + CIFTCI_CYCLE_INTERVAL_MS;
1304
+ nextCiftciTimeStr = `${getFutureDateTimeString(CIFTCI_CYCLE_INTERVAL_MS)} (${formatDurationMs(CIFTCI_CYCLE_INTERVAL_MS)} sonra)`;
1305
+ logger.info(`[Çiftçi Periyodik] Bağımsız döngü başlatıldı (her ${formatDurationMs(CIFTCI_CYCLE_INTERVAL_MS)}).`);
1306
  }
1307
 
1308
  bot.once("spawn", async () => {
1309
+ isConnecting = false;
1310
  if (!bot) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1311
 
1312
+ try {
1313
+ logger.info("Bot sunucuya giriş yaptı.");
1314
+ botIsReady = true;
1315
+ botUptimeStart = Date.now();
1316
+
1317
+ // Express panelinin bu bot örneğine erişebilmesi için köprüyü doldur.
1318
+ botActions.scanAndFeedManual = scanAndFeedManual;
1319
+ botActions.scanAndCollectMinersManual = scanAndCollectMinersManual;
1320
+ botActions.openCiftciAndSellAllManual = openCiftciAndSellAllManual;
1321
+ botActions.goToIslandManual = goToIsland;
1322
+ botActions.sendSkyblockManual = sendSkyblockCommand;
1323
+
1324
+ // Kazıcı ve Çiftçi bağımsız döngülerini başlat (birbirlerinden ve besleyiciden
1325
+ // bağımsız zamanlayıcılar; sadece guiMutex ile çakışma önlenir).
1326
+ startKaziciPeriodic();
1327
+ startCiftciPeriodic();
1328
+
1329
+ if (config.utils && config.utils["auto-auth"] && config.utils["auto-auth"].enabled) {
1330
+ const password = config.utils["auto-auth"].password;
1331
+
1332
+ setSafeTimeout(() => {
1333
+ try {
1334
+ if (bot && botIsReady) {
1335
+ bot.chat(`/login ${password}`);
1336
+ logger.info(`[${currentBotUsername}] Login komutu kullanıldı.`);
1337
+ }
1338
+ } catch (err) {
1339
+ logger.error(`[Spawn Hatası] /login komutu gönderilemedi: ${err && err.message ? err.message : err}`);
1340
+ }
1341
+ }, 2500);
1342
 
1343
+ setSafeTimeout(() => {
1344
+ try {
1345
+ if (bot && botIsReady) {
1346
+ bot.chat(`/skyblock`);
1347
+ logger.info(`[${currentBotUsername}] Skyblock'a ışınlanma komutu kullanıldı.`);
1348
+ }
1349
+ } catch (err) {
1350
+ logger.error(`[Spawn Hatası] /skyblock komutu gönderilemedi: ${err && err.message ? err.message : err}`);
1351
+ }
1352
+ }, 7500);
1353
+
1354
+ // İlk çalıştırma: Girişten 12.5 saniye sonra ilk besleyici kontrolünü yap
1355
+ setSafeTimeout(async () => {
1356
+ if (!bot || !botIsReady) return;
1357
+ isMainCycleRunning = true;
1358
+ try {
1359
+ await goToIsland();
1360
+ await new Promise(r => setTimeout(r, HOME_ARRIVAL_DELAY_MS));
1361
+ await scanAndFeed();
1362
+ // NOT: Kazıcı ve çiftçi toplama burada da çağrılmıyor; kendi bağımsız
1363
+ // döngüleri zaten spawn olur olmaz başladı.
1364
+ } catch (err) {
1365
+ logger.error(`[Spawn Hatası] İlk kontrol döngüsünde beklenmeyen hata: ${err && err.stack ? err.stack : err}`);
1366
+ try { scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } catch (e2) {}
1367
+ } finally {
1368
+ isMainCycleRunning = false;
1369
+ }
1370
+ }, 12500);
1371
+ }
1372
+ } catch (err) {
1373
+ logger.error(`[Spawn Hatası] Spawn işleyicisinde beklenmeyen hata: ${err && err.stack ? err.stack : err}`);
1374
  }
1375
  });
1376
 
1377
  bot.on("windowOpen", async (window) => {
1378
  if (!bot) return;
1379
+ try {
1380
+ const titleRaw = parseWindowTitleToString(window.title);
1381
+
1382
+ const isBesleyiciWindow = matchesAnyName(titleRaw, TARGET_NAME_NORMS);
1383
+ const isKaziciWindow = !isBesleyiciWindow && matchesAnyName(titleRaw, TARGET_KAZICI_NORMS);
1384
+ const isCiftciWindow = !isBesleyiciWindow && !isKaziciWindow &&
1385
+ (matchesAnyName(titleRaw, TARGET_CIFTCI_NORMS) || windowMatchesLoreKeyword(window, CIFTCI_LORE_FALLBACK_KEYWORDS));
1386
+
1387
+ if (!isBesleyiciWindow && !isKaziciWindow && !isCiftciWindow) {
1388
+ logger.info(`[${currentBotUsername}] Bi chest açıldı ama window title ı uyuşmadığı için kapatıldı. ${titleRaw}`);
1389
+ try { bot.closeWindow(window); } catch(e){}
1390
+ return;
1391
+ }
1392
+
1393
+ if (isKaziciWindow) {
1394
+ try {
1395
+ 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...`);
1396
+ await bot.clickWindow(KAZICI_STORAGE_TRANSFER_SLOT, 0, 0);
1397
+ logger.info(`[${currentBotUsername}] Kazıcı depo aktarım tıklaması gönderildi.`);
1398
+ await new Promise(r => setTimeout(r, KAZICI_ACTION_SETTLE_DELAY_MS));
1399
+ } catch (e) {
1400
+ logger.error(`[Kazıcı Arayüz Hatası] Depo aktarım tıklaması sunucu tarafından onaylanmadı: ${e && e.message ? e.message : e}`);
 
1401
  }
1402
+
1403
+ try { bot.closeWindow(window); } catch(e){}
1404
+
1405
+ } else if (isCiftciWindow) {
1406
+ // Pencere açılır açılmaz hemen tıklamak yerine, sunucunun menü içeriğini
1407
+ // (lore/slot verisini) tam yerleştirmesi için önce kısa bir süre beklenir,
1408
+ // ardından "hepsini sat" slotuna birkaç kez art arda tıklanır. Bu, bazen
1409
+ // satışın hiç gerçekleşmemesi sorununu (erken tıklama nedeniyle sunucunun
1410
+ // isteği görmezden gelmesi) engellemek içindir.
1411
+ try {
1412
+ 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...`);
1413
+ await new Promise(r => setTimeout(r, CIFTCI_CLICK_DELAY_AFTER_OPEN_MS));
1414
+
1415
+ for (let i = 0; i < CIFTCI_CLICK_REPEAT_COUNT; i++) {
1416
+ if (!bot || !botIsReady) break;
1417
+ try {
1418
+ await bot.clickWindow(CIFTCI_SELL_ALL_SLOT, 0, 0);
1419
+ logger.info(`[${currentBotUsername}] Çiftçi hepsini-sat tıklaması gönderildi (${i + 1}/${CIFTCI_CLICK_REPEAT_COUNT}).`);
1420
+ } catch (clickErr) {
1421
+ logger.error(`[Çiftçi Arayüz Hatası] Tıklama #${i + 1} sunucu tarafından onaylanmadı: ${clickErr && clickErr.message ? clickErr.message : clickErr}`);
1422
+ }
1423
+ if (i < CIFTCI_CLICK_REPEAT_COUNT - 1) {
1424
+ await new Promise(r => setTimeout(r, CIFTCI_CLICK_REPEAT_GAP_MS));
1425
+ }
1426
+ }
1427
+
1428
+ await new Promise(r => setTimeout(r, KAZICI_ACTION_SETTLE_DELAY_MS));
1429
+ } catch (e) {
1430
+ logger.error(`[Çiftçi Arayüz Hatası] Hepsini-sat tıklama turu sırasında beklenmeyen hata: ${e && e.message ? e.message : e}`);
1431
+ }
1432
+
1433
+ try { bot.closeWindow(window); } catch(e){}
1434
 
1435
  } else {
1436
+ const beefSlot = window.slots.find(s => s?.name === "cooked_beef");
1437
+ const can = extractMinyonCanFromSlot(beefSlot);
1438
+
1439
+ if (can != null) {
1440
+ const currentStamp = getExtendedDateTimeString();
1441
+ lastCheckTimeStr = `${currentStamp} (Okunan Can: ${can})`;
1442
+ lastCheckAtMs = Date.now();
1443
+
1444
+ if (can <= 2) {
1445
+ try {
1446
+ logger.info(`[${currentBotUsername}] Minyon canı ${can} olduğu için besleniyor...`);
1447
+ await bot.clickWindow(36, 0, 0);
1448
+ lastFullFeedAt = Date.now();
1449
+ lastFullFeedTimeStr = `${currentStamp} (Minyon canı ${can} iken başarıyla beslendi)`;
1450
+ logger.info(`[${currentBotUsername}] Tıklama onaylandı. Fixed ${formatDurationMs(FULL_FEED_COOLDOWN_MS)} bekleme aktif.`);
1451
+ } catch(e) {
1452
+ logger.error(`[Arayüz Hatası] Tıklama işlemi sunucu tarafından onaylanmadı: ${e.message}`);
1453
+ }
1454
+ scheduleNextCheck(FULL_FEED_COOLDOWN_MS);
1455
+
1456
+ } else {
1457
+ let calculatedMinutes = (can - 2) * 15;
1458
+ calculatedMinutes = Math.min(65, Math.max(15, calculatedMinutes));
1459
+
1460
+ const dynamicDelayMs = calculatedMinutes * 60 * 1000;
1461
+
1462
+ logger.info(`[${currentBotUsername}] Minyon canı ${can} olarak okundu. Canın 2'ye düşmesi için ${formatDurationMs(dynamicDelayMs)} beklenecek.`);
1463
+
1464
+ if (can >= 10) {
1465
+ lastFullFeedTimeStr = `${currentStamp} (Canı ${can} olduğu için besleme gerekmedi)`;
1466
+ }
1467
+
1468
+ scheduleNextCheck(dynamicDelayMs);
1469
+ }
1470
+ } else {
1471
+ logger.warn(`[${currentBotUsername}] Menü açıldı ama can okunamadı. ${formatDurationMs(DEFAULT_CHECK_INTERVAL_MS)} sonra tekrar denenecek.`);
1472
+ scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS);
1473
  }
1474
+
1475
+ try { bot.closeWindow(window); } catch(e){}
1476
  }
1477
+ } catch (err) {
1478
+ logger.error(`[windowOpen Hatası] Beklenmeyen bir hata oluştu: ${err && err.stack ? err.stack : err}`);
1479
+ try { bot.closeWindow(window); } catch(e){}
1480
+ try { scheduleNextCheck(DEFAULT_CHECK_INTERVAL_MS); } catch (e2) {}
1481
  }
1482
 
 
 
1483
  while (windowOpenResolvers.length > 0) {
1484
  const fn = windowOpenResolvers.shift();
1485
+ if (typeof fn === 'function') {
1486
+ try { fn(); } catch (e) {}
1487
+ }
1488
  }
1489
  });
1490
 
1491
  bot.on("end", () => {
1492
+ try {
1493
+ 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...");
1494
+ stopPeriodicHomeChecks();
1495
+ lastCheckAtMs = null;
1496
+ botIsReady = false;
1497
+ baglantiTuru = "Yok";
1498
+ botUptimeStart = null;
1499
+ isListeningChat = false;
1500
+ activeConnectionIp = "Bilinmiyor";
1501
+ activeConnectionLocation = "Bilinmiyor";
1502
+ nextScheduledCheckTimeStr = manualStopRequested ? "Bot manuel olarak durduruldu." : "Bağlantı koptuğu için planlama iptal edildi.";
1503
+ nextKaziciTimeStr = manualStopRequested ? "Bot manuel olarak durduruldu." : "Bağlantı koptuğu için planlama iptal edildi.";
1504
+ nextCiftciTimeStr = manualStopRequested ? "Bot manuel olarak durduruldu." : "Bağlantı koptuğu için planlama iptal edildi.";
1505
+
1506
+ botActions.scanAndFeedManual = null;
1507
+ botActions.scanAndCollectMinersManual = null;
1508
+ botActions.openCiftciAndSellAllManual = null;
1509
+ botActions.goToIslandManual = null;
1510
+ botActions.sendSkyblockManual = null;
1511
 
1512
+ safeDestroyAllConnections();
1513
+
1514
+ if (!manualStopRequested && config.utils && config.utils["auto-reconnect"]) {
1515
+ triggerSafeReconnect();
1516
+ }
1517
+ } catch (err) {
1518
+ logger.error(`['end' Hatası] Bağlantı kopma işlemi sırasında beklenmeyen hata: ${err && err.stack ? err.stack : err}`);
1519
  }
1520
  });
1521
 
1522
  bot.on("kicked", (reason) => logger.warn(`Bot sunucudan atıldı: ${util.inspect(reason)}`));
1523
  bot.on("error", (err) => logger.error(`Mineflayer Hatası: ${err}`));
1524
+
1525
+ } catch (err) {
1526
+ logger.error(`[createBot Hatası] Beklenmeyen bir hata oluştu, bağlantı denemesi güvenle iptal edildi: ${err && err.stack ? err.stack : err}`);
1527
+ isConnecting = false;
1528
+ botIsReady = false;
1529
+ baglantiTuru = "Yok";
1530
+
1531
+ if (!manualStopRequested && config.utils && config.utils["auto-reconnect"]) {
1532
+ triggerSafeReconnect();
1533
+ }
1534
+ }
1535
  }
1536
 
1537
  function safeDestroyAllConnections() {
1538
  if (isDestroying) return;
1539
+ isDestroying = true;
1540
+ isConnecting = false;
1541
+
1542
  windowOpenResolvers = [];
1543
 
1544
  if (viewerInstance && typeof viewerInstance.close === 'function') {
1545
+ try {
1546
  if (viewerInstance.ws && typeof viewerInstance.ws.close === 'function') {
1547
  viewerInstance.ws.close();
1548
  }
1549
+ viewerInstance.close();
1550
  } catch(e){}
1551
  }
1552
  viewerInstance = null;
 
1557
  }
1558
 
1559
  if (currentSshClient && typeof currentSshClient.destroy === 'function') {
1560
+ try { currentSshClient.destroy(); } catch(e){}
1561
  } else {
1562
  if (currentSocket && typeof currentSocket.destroy === 'function') {
1563
  try { currentSocket.destroy(); } catch(e){}
 
1570
 
1571
  if (bot) {
1572
  try {
1573
+ bot.removeAllListeners();
1574
+ bot.quit();
1575
+ } catch(e){}
 
 
 
1576
  bot = null;
1577
  }
1578
  }
 
1582
  clearTimeout(reconnectTimeout);
1583
  }
1584
  const delay = (config.utils && config.utils["auto-reconnect-delay"]) || 5000;
1585
+ logger.info(`[Sistem] ${formatDurationMs(delay)} sonra tekil bir hat üzerinden sistem sıfırdan başlatılacak...`);
1586
+
1587
  reconnectTimeout = setTimeout(() => {
1588
+ createBot();
1589
  }, delay);
1590
  }
1591
 
1592
+ // --- MANUEL BOT KONTROLÜ (stop/start/restart) ---
1593
+ let pendingHardRestartTimeout = null;
1594
+
1595
+ function stopBotHard(reason) {
1596
+ try {
1597
+ logger.warn(`[Manuel Kontrol] Bot durduruluyor. Sebep: ${reason || "Elle durdurma istendi"}`);
1598
+ manualStopRequested = true;
1599
+
1600
+ if (reconnectTimeout) {
1601
+ clearTimeout(reconnectTimeout);
1602
+ reconnectTimeout = null;
1603
+ }
1604
+ if (pendingHardRestartTimeout) {
1605
+ clearTimeout(pendingHardRestartTimeout);
1606
+ pendingHardRestartTimeout = null;
1607
+ }
1608
+
1609
+ stopPeriodicHomeChecks();
1610
+ lastCheckAtMs = null;
1611
+ safeDestroyAllConnections();
1612
+
1613
+ botIsReady = false;
1614
+ baglantiTuru = "Yok";
1615
+ botUptimeStart = null;
1616
+ activeConnectionIp = "Bilinmiyor";
1617
+ activeConnectionLocation = "Bilinmiyor";
1618
+ nextScheduledCheckTimeStr = "Bot manuel olarak durduruldu.";
1619
+ nextKaziciTimeStr = "Bot manuel olarak durduruldu.";
1620
+ nextCiftciTimeStr = "Bot manuel olarak durduruldu.";
1621
+ } catch (err) {
1622
+ logger.error(`[stopBotHard Hatası] ${err && err.stack ? err.stack : err}`);
1623
+ }
1624
+ }
1625
+
1626
+ function startBotHard(reason) {
1627
+ try {
1628
+ logger.info(`[Manuel Kontrol] Bot başlatılıyor. Sebep: ${reason || "Elle başlatma istendi"}`);
1629
+ manualStopRequested = false;
1630
+
1631
+ if (bot || isConnecting) {
1632
+ safeDestroyAllConnections();
1633
+ }
1634
+ stopPeriodicHomeChecks();
1635
+
1636
+ createBot();
1637
+ } catch (err) {
1638
+ logger.error(`[startBotHard Hatası] ${err && err.stack ? err.stack : err}`);
1639
+ }
1640
+ }
1641
+
1642
+ function restartBotHard(reason) {
1643
+ logger.warn(`[Manuel Kontrol] Bot tamamen sıfırdan yeniden başlatılıyor (hard restart). Sebep: ${reason || "Elle restart istendi"}`);
1644
+ stopBotHard(reason);
1645
+
1646
+ if (pendingHardRestartTimeout) {
1647
+ clearTimeout(pendingHardRestartTimeout);
1648
+ }
1649
+
1650
+ pendingHardRestartTimeout = setTimeout(() => {
1651
+ pendingHardRestartTimeout = null;
1652
+ startBotHard(reason);
1653
+ }, 750);
1654
+ }
1655
+
1656
  function globalMinecraftMessageListener(jsonMsg) {
1657
  try {
1658
  if (!bot) return;
 
1678
  const app = express();
1679
  const http = require('http');
1680
 
1681
+ app.get("/logs", (req, res) => {
1682
+ const logs = getRecentLogs();
1683
+
1684
+ res.send({
1685
+ server_time: new Date().toLocaleString('tr-TR'),
1686
+ range_minutes: 30,
1687
+ count: logs.length,
1688
+ logs: logs.map(l => ({
1689
+ time: new Date(l.time).toLocaleTimeString('tr-TR'),
1690
+ level: l.level,
1691
+ message: l.message
1692
+ }))
1693
+ });
1694
+ });
1695
+
1696
+ app.get("/logs/view", (req, res) => {
1697
+ const html = `
1698
+ <!DOCTYPE html>
1699
+ <html>
1700
+ <head>
1701
+ <meta charset="UTF-8">
1702
+ <title>Sistem Logları</title>
1703
+ <style>
1704
+ body { background-color: #111; color: #eee; font-family: 'Courier New', Courier, monospace; padding: 20px; margin: 0; }
1705
+ .container { max-width: 1100px; margin: 0 auto; background: #1a1a1a; padding: 15px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.5); }
1706
+ h2 { margin-top: 0; border-bottom: 1px solid #333; padding-bottom: 10px; color: #fff; display: flex; justify-content: space-between; align-items: center; }
1707
+ h2 span { font-size: 13px; color: #777; font-weight: normal; }
1708
+ #log-box { height: 600px; overflow-y: auto; background: #000; border: 1px solid #333; padding: 10px; border-radius: 4px; display: flex; flex-direction: column; }
1709
+ .log-line { margin-bottom: 4px; line-height: 1.5; font-size: 13px; white-space: pre-wrap; word-break: break-all; }
1710
+ .time-tag { color: #555; margin-right: 8px; }
1711
+ .lvl-INFO { color: #7fd1ff; }
1712
+ .lvl-WARN { color: #ffcc66; }
1713
+ .lvl-ERROR { color: #ff6b6b; }
1714
+ .lvl-FATAL { color: #ff3333; font-weight: bold; }
1715
+ .lvl-DEBUG, .lvl-TRACE { color: #999; }
1716
+ </style>
1717
+ </head>
1718
+ <body>
1719
+ <div class="container">
1720
+ <h2>Sistem Logları <span id="meta">son 30 dakika</span></h2>
1721
+ <div id="log-box"></div>
1722
+ </div>
1723
+ <script>
1724
+ async function updateLogs() {
1725
+ try {
1726
+ const res = await fetch('/logs');
1727
+ if (!res.ok) return;
1728
+ const data = await res.json();
1729
+ const box = document.getElementById('log-box');
1730
+ const meta = document.getElementById('meta');
1731
+ meta.textContent = 'son 30 dakika · ' + data.count + ' kayıt · güncelleme: ' + data.server_time;
1732
+
1733
+ const wasAtBottom = box.scrollHeight - box.scrollTop - box.clientHeight < 30;
1734
+
1735
+ box.innerHTML = '';
1736
+ if (data.logs && data.logs.length > 0) {
1737
+ data.logs.forEach(l => {
1738
+ const div = document.createElement('div');
1739
+ div.className = 'log-line';
1740
+ const lvl = (l.level || 'info').toUpperCase();
1741
+ div.innerHTML = '<span class="time-tag">[' + l.time + ']</span><span class="lvl-' + lvl + '">[' + lvl + ']</span> ' + l.message.replace(/</g, '&lt;');
1742
+ box.appendChild(div);
1743
+ });
1744
+ if (wasAtBottom) box.scrollTop = box.scrollHeight;
1745
+ } else {
1746
+ box.innerHTML = '<div style="color:#555;">Son 30 dakikada log kaydı yok.</div>';
1747
+ }
1748
+ } catch (e) {}
1749
+ }
1750
+ setInterval(updateLogs, 3000);
1751
+ updateLogs();
1752
+ </script>
1753
+ </body>
1754
+ </html>
1755
+ `;
1756
+ res.send(html);
1757
+ });
1758
+
1759
  app.get("/status", (req, res) => {
1760
  let uptimeString = "Bağlı Değil";
1761
  if (botIsReady && botUptimeStart) {
1762
+ uptimeString = formatDurationMs(Date.now() - botUptimeStart);
1763
+ }
1764
+
1765
+ res.send({
1766
+ ready: botIsReady,
 
 
 
 
 
 
 
1767
  username: bot ? bot.username : null,
1768
  connection_type: baglantiTuru,
1769
  connection_ip: activeConnectionIp,
1770
  connection_geo: activeConnectionLocation,
1771
  bot_uptime: uptimeString,
1772
+ last_minyon_check: lastCheckTimeStr,
1773
  last_minyon_feed: lastFullFeedTimeStr,
1774
+ next_scheduled_check: nextScheduledCheckTimeStr,
1775
+ last_kazici_check: lastKaziciCheckTimeStr,
1776
+ next_kazici_check: nextKaziciTimeStr,
1777
+ last_ciftci_sell: lastCiftciSellTimeStr,
1778
+ next_ciftci_sell: nextCiftciTimeStr,
1779
+ gui_lock_busy: guiMutex.isLocked(),
1780
+ manually_stopped: manualStopRequested,
1781
+ is_connecting: isConnecting
1782
  });
1783
  });
1784
 
1785
+ // --- MANUEL BOT KONTROLÜ ENDPOINT'LERİ ---
1786
+ app.get("/bot/stop", (req, res) => {
1787
+ stopBotHard("Web paneli üzerinden /bot/stop isteği");
1788
+ res.send({ success: true, message: "Bot durduruldu. Tekrar başlatmak için /bot/start kullan." });
1789
+ });
1790
+
1791
+ app.get("/bot/start", (req, res) => {
1792
+ if (bot && botIsReady) {
1793
+ return res.send({ success: false, message: "Bot zaten çalışıyor. Sıfırdan başlatmak için /bot/restart kullan." });
1794
+ }
1795
+ startBotHard("Web paneli üzerinden /bot/start isteği");
1796
+ res.send({ success: true, message: "Bot başlatılıyor." });
1797
+ });
1798
+
1799
+ app.get("/bot/restart", (req, res) => {
1800
+ restartBotHard("Web paneli üzerinden /bot/restart isteği");
1801
+ res.send({ success: true, message: "Bot tamamen sıfırdan yeniden başlatılıyor (soket/SSH dahil)." });
1802
+ });
1803
+
1804
+ // --- YENİ: MANUEL GUI/AKSİYON TETİKLEME ENDPOINT'LERİ ---
1805
+ // Bu endpoint'ler istek geldiği anda hemen yanıt döner (fire-and-forget), asıl
1806
+ // işlem arka planda ÖNCELİKLİ olarak (guiMutex.acquire ile kuyruğa öncelikli
1807
+ // girerek) yürütülür. O sırada otomatik bir döngü (besleyici, kazıcı veya çiftçi)
1808
+ // zaten çalışıyorsa, önce o tamamlanır, HEMEN ARDINDAN bu istek işlenir.
1809
+
1810
+ app.get("/bot/kazici-kontrol", (req, res) => {
1811
+ if (!bot || !botIsReady || !botActions.scanAndCollectMinersManual) {
1812
+ return res.status(503).send({ success: false, message: "Bot hazır değil." });
1813
+ }
1814
+ res.send({ success: true, message: "Kazıcı kontrolü öncelikli olarak kuyruğa alındı, işleniyor." });
1815
+ botActions.scanAndCollectMinersManual().catch(e => logger.error(`[/bot/kazici-kontrol Hatası] ${e && e.stack ? e.stack : e}`));
1816
+ });
1817
+
1818
+ app.get("/bot/ciftci-satis", (req, res) => {
1819
+ if (!bot || !botIsReady || !botActions.openCiftciAndSellAllManual) {
1820
+ return res.status(503).send({ success: false, message: "Bot hazır değil." });
1821
+ }
1822
+ res.send({ success: true, message: "Çiftçi satışı öncelikli olarak kuyruğa alındı, işleniyor." });
1823
+ botActions.openCiftciAndSellAllManual().catch(e => logger.error(`[/bot/ciftci-satis Hatası] ${e && e.stack ? e.stack : e}`));
1824
+ });
1825
+
1826
+ app.get("/bot/besleyici-kontrol", (req, res) => {
1827
+ if (!bot || !botIsReady || !botActions.scanAndFeedManual) {
1828
+ return res.status(503).send({ success: false, message: "Bot hazır değil." });
1829
+ }
1830
+ res.send({ success: true, message: "Besleyici kontrolü öncelikli olarak kuyruğa alındı, işleniyor." });
1831
+ botActions.scanAndFeedManual().catch(e => logger.error(`[/bot/besleyici-kontrol Hatası] ${e && e.stack ? e.stack : e}`));
1832
+ });
1833
+
1834
+ app.get("/bot/skyblock", (req, res) => {
1835
+ if (!bot || !botIsReady || !botActions.sendSkyblockManual) {
1836
+ return res.status(503).send({ success: false, message: "Bot hazır değil." });
1837
+ }
1838
+ botActions.sendSkyblockManual().catch(e => logger.error(`[/bot/skyblock Hatası] ${e && e.stack ? e.stack : e}`));
1839
+ res.send({ success: true, message: "/skyblock komutu gönderildi." });
1840
+ });
1841
+
1842
+ app.get("/bot/is-go", (req, res) => {
1843
+ if (!bot || !botIsReady || !botActions.goToIslandManual) {
1844
+ return res.status(503).send({ success: false, message: "Bot hazır değil." });
1845
+ }
1846
+ botActions.goToIslandManual().catch(e => logger.error(`[/bot/is-go Hatası] ${e && e.stack ? e.stack : e}`));
1847
+ res.send({ success: true, message: `${GO_ISLAND_COMMAND} komutu gönderildi.` });
1848
+ });
1849
+
1850
  app.get("/", (req, res) => res.send("Bot Aktif"));
1851
 
1852
  app.get("/messages", (req, res) => {
 
1873
  messagesTimeout = setTimeout(() => {
1874
  activeMessages = [];
1875
  messagesTimeout = null;
1876
+
1877
  if (bot && isListeningChat) {
1878
  bot.removeListener('message', globalMinecraftMessageListener);
1879
  isListeningChat = false;
 
1944
  if (!viewerInstance && isBotReady) {
1945
  try {
1946
  mineflayerViewer(bot, { port: 3000, firstPerson: true });
1947
+ viewerInstance = bot.viewer;
1948
  logger.info("[Viewer] 3D Harita motoru arka planda (3000) başlatıldı.");
1949
  setTimeout(() => next(), 600);
1950
  return;
 
1987
 
1988
  app.get("/restart", (req, res) => {
1989
  logger.info("[Sistem] Web paneli üzerinden tam restart isteği geldi. Sistem felç ediliyor...");
1990
+
1991
  if (config.utils) {
1992
  config.utils["auto-reconnect"] = false;
1993
  }
1994
+ isDestroying = true;
1995
 
1996
  if (bot && botIsReady) {
1997
  try { bot.chat("Bot web paneli uzerinden tum kodlariyla yeniden baslatiliyor..."); } catch(e){}
1998
  res.send({ success: true, message: "Tüm proje kapatıldı. Konteyner birkaç saniye içinde sıfırdan başlayacak." });
1999
+
2000
  setTimeout(() => {
2001
  try { bot.quit(); } catch(e){}
2002
  safeDestroyAllConnections();
logging.js CHANGED
@@ -1,5 +1,7 @@
1
  process.env.TZ = "Europe/Istanbul";
2
  const log4js = require("log4js");
 
 
3
  log4js.configure({
4
  appenders: {
5
  console: { type: "console" },
@@ -20,6 +22,68 @@ log4js.configure({
20
 
21
  const logger = log4js.getLogger();
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  module.exports = {
24
- logger
 
25
  }
 
1
  process.env.TZ = "Europe/Istanbul";
2
  const log4js = require("log4js");
3
+ const util = require("util");
4
+
5
  log4js.configure({
6
  appenders: {
7
  console: { type: "console" },
 
22
 
23
  const logger = log4js.getLogger();
24
 
25
+ // --- WEB PANEL İÇİN BELLEK İÇİ LOG TAMPONU ---
26
+ // log4js'in kendisi "son X dakikanın logları" diye bir şey sunmuyor, o yüzden
27
+ // her log çağrısını (console'a yazmaya ek olarak) burada da saklıyoruz.
28
+ // Varsayılan tutma süresi 30 dakika; bu sürenin dışındaki kayıtlar otomatik atılır.
29
+ const LOG_RETENTION_MS = 30 * 60 * 1000;
30
+ const MAX_BUFFER_SIZE = 5000; // Aşırı log patlaması durumunda RAM'i sınırlamak için güvenlik tavanı
31
+
32
+ let logBuffer = [];
33
+
34
+ function pruneOldLogs() {
35
+ const cutoff = Date.now() - LOG_RETENTION_MS;
36
+ while (logBuffer.length && logBuffer[0].time < cutoff) {
37
+ logBuffer.shift();
38
+ }
39
+ if (logBuffer.length > MAX_BUFFER_SIZE) {
40
+ logBuffer.splice(0, logBuffer.length - MAX_BUFFER_SIZE);
41
+ }
42
+ }
43
+
44
+ function formatArgs(args) {
45
+ try {
46
+ return args
47
+ .map((a) => (typeof a === "string" ? a : util.inspect(a, { depth: 2 })))
48
+ .join(" ");
49
+ } catch (e) {
50
+ return "[loglanamayan mesaj]";
51
+ }
52
+ }
53
+
54
+ function pushToBuffer(level, args) {
55
+ try {
56
+ logBuffer.push({
57
+ time: Date.now(),
58
+ level,
59
+ message: formatArgs(args)
60
+ });
61
+ pruneOldLogs();
62
+ } catch (e) {
63
+ // Log tamponuna yazarken bile bir hata olursa asıl process'i etkilemesin
64
+ }
65
+ }
66
+
67
+ // logger.info / warn / error / debug / trace / fatal çağrılarını, orijinal
68
+ // davranışlarını bozmadan, aynı zamanda tampona da yazacak şekilde sarmalıyoruz.
69
+ ["trace", "debug", "info", "warn", "error", "fatal"].forEach((level) => {
70
+ if (typeof logger[level] !== "function") return;
71
+ const original = logger[level].bind(logger);
72
+ logger[level] = (...args) => {
73
+ pushToBuffer(level, args);
74
+ return original(...args);
75
+ };
76
+ });
77
+
78
+ // Web panelden çağrılacak: HER ZAMAN son 30 dakikanın loglarını döner.
79
+ // Kasıtlı olarak parametre almıyor — süre dışarıdan (istekle) değiştirilemesin,
80
+ // tampon zaten her yazımda 30 dk'dan eskisini otomatik siliyor (RAM güvenliği).
81
+ function getRecentLogs() {
82
+ const cutoff = Date.now() - LOG_RETENTION_MS;
83
+ return logBuffer.filter((l) => l.time >= cutoff);
84
+ }
85
+
86
  module.exports = {
87
+ logger,
88
+ getRecentLogs
89
  }