Auto Recruit Units Pros (langsung run jika history kosong)

Recruit otomatis dengan interval acak, resume setelah reload, dan run langsung jika histori kosong

// ==UserScript==
// @name         Auto Recruit Units Pros (langsung run jika history kosong)
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  Recruit otomatis dengan interval acak, resume setelah reload, dan run langsung jika histori kosong
// @match        https://en149.tribalwars.net/game.php?village=*&screen=train*
// @match        https://en149.tribalwars.net/game.php?village=*&screen=train&*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  /*** ================== KONFIG ================== ***/
  const SPEAR_COUNT = 6;
  const LIGHT_COUNT = 3;

  // Interval acak 30–37 menit (ms)
  const MIN_MINUTES = 30;
  const MAX_MINUTES = 37;

  /*** ============== UTIL & STORAGE =============== ***/
  const qs = new URLSearchParams(window.location.search);
  const villageId = qs.get('village') || 'unknown';
  const STORAGE_KEY_BASE = `TW_AUTO_RECRUIT_${villageId}`;
  const LS = {
    lastRun: `${STORAGE_KEY_BASE}_LAST_RUN_TS`,
    lastInterval: `${STORAGE_KEY_BASE}_LAST_INTERVAL_MS`,
  };

  const now = () => Date.now();
  const ts = (t = now()) => new Date(t).toLocaleString();

  const log = (m, x) => x === undefined
    ? console.log(`[${ts()}] ${m}`)
    : console.log(`[${ts()}] ${m}`, x);

  const readInt = (k) => {
    const v = localStorage.getItem(k);
    if (v == null) return null;
    const n = Number(v);
    return Number.isFinite(n) ? n : null;
  };

  const saveLastRun = (t) => localStorage.setItem(LS.lastRun, String(t));
  const saveLastInterval = (ms) => localStorage.setItem(LS.lastInterval, String(ms));
  const getLastRun = () => readInt(LS.lastRun);
  const getLastInterval = () => readInt(LS.lastInterval);

  const nextIntervalMs = () => {
    const minutes = Math.floor(Math.random() * (MAX_MINUTES - MIN_MINUTES + 1)) + MIN_MINUTES;
    const ms = minutes * 60 * 1000;
    log(`Interval baru diundi: ${minutes} menit (${ms} ms).`);
    return ms;
  };

  /*** ============== LOGIKA REKRUT ================ ***/
  function findElements() {
    const spearInput = document.querySelector('input[name="spear"]');
    const lightInput = document.querySelector('input[name="light"]');

    const recruitButton =
      document.querySelector('.btn-recruit[value="Recruit"]') ||
      document.querySelector('input.btn-recruit[type="submit"]') ||
      document.querySelector('button.btn-recruit') ||
      document.querySelector('input[type="submit"][value="Recruit"]');

    return { spearInput, lightInput, recruitButton };
  }

  function recruitOnce() {
    const { spearInput, lightInput, recruitButton } = findElements();
    if (!spearInput || !lightInput || !recruitButton) {
      log('Elemen form tidak lengkap, rekrut gagal.');
      return false;
    }
    spearInput.value = String(SPEAR_COUNT);
    lightInput.value = String(LIGHT_COUNT);
    recruitButton.click();
    log(`Rekrut dijalankan (${SPEAR_COUNT} spear, ${LIGHT_COUNT} light).`);
    return true;
  }

  /*** ============== PENJADWALAN ================== ***/
  function scheduleNext(delayMs) {
    const min = (delayMs / 60000).toFixed(2);
    log(`Menjadwalkan eksekusi berikutnya dalam ${min} menit (${delayMs} ms).`);

    setTimeout(() => {
      const ok = recruitOnce();
      if (ok) {
        const t = now();
        saveLastRun(t);
        log(`lastRun disimpan: ${ts(t)}`);
      }
      const interval = nextIntervalMs();
      saveLastInterval(interval);
      scheduleNext(interval);
    }, Math.max(0, delayMs));
  }

  /*** ================== INIT ====================== ***/
  function init() {
    log('Script initialized (langsung run jika histori kosong).');

    const lastRun = getLastRun();
    let lastInterval = getLastInterval();

    if (lastRun == null || lastInterval == null) {
      log('Histori kosong → jalankan rekrut pertama sekarang.');
      const ok = recruitOnce();
      if (ok) {
        const t = now();
        saveLastRun(t);
        log(`lastRun disimpan: ${ts(t)}`);
      }
      const interval = nextIntervalMs();
      saveLastInterval(interval);
      scheduleNext(interval);
      return;
    }

    const elapsed = now() - lastRun;
    const remaining = lastInterval - elapsed;
    log(
      `Histori ditemukan. lastRun: ${ts(lastRun)}, lastInterval: ${(lastInterval / 60000).toFixed(
        2
      )} menit, elapsed: ${(elapsed / 60000).toFixed(2)} menit, remaining: ${(Math.max(0, remaining) / 60000).toFixed(
        2
      )} menit.`
    );

    if (remaining <= 0) {
      log('Interval terlewati → jalankan rekrut segera.');
      const ok = recruitOnce();
      if (ok) {
        const t = now();
        saveLastRun(t);
      }
      const interval = nextIntervalMs();
      saveLastInterval(interval);
      scheduleNext(interval);
    } else {
      scheduleNext(remaining);
    }
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

  // ⚠️ Catatan: Otomasi bisa melanggar ToS game & berisiko ban.
})();