Shinigami Auto Scroll

Auto scroll otomatis buat baca manga di Shinigami, lengkap dengan kontrol speed dan UI floating panel.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Shinigami Auto Scroll
// @namespace    https://greasyfork.org/users/SyaizZ
// @version      1.0.3
// @description  Auto scroll otomatis buat baca manga di Shinigami, lengkap dengan kontrol speed dan UI floating panel.
// @author       SyaizZ
// @match        https://*.shinigami.asia/chapter/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=shinigami.asia
// @grant        GM_addStyle
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
  let scrolling = false;
  let animFrame;
  let speed = 3;
  let navbarVisible = true;

  function getOriPanel() {
    return document.querySelector('.fixed.bottom-0.left-0.w-screen');
  }

  function getOriSettingBtn() {
    const nb = document.querySelector('div.sticky.z-50');
    if (!nb) return null;
    return nb.querySelectorAll('button')[1];
  }

  function injectPlayToPanel() {
    const panel = getOriPanel();
    if (!panel) return;
    if (document.getElementById('sng-inject-play')) return;

    // Cek apakah ini versi prem (ada svg circle = icon play premium)
    const isPrem = !!panel.querySelector('svg circle[r="41.5"]');

    if (isPrem) {
      // Hide konten prem
      const premDiv = panel.querySelector('.text-center.text-base-white.gap-32');
      if (premDiv) premDiv.style.display = 'none';
    }

    // Cari save button row buat inject sebelumnya
    const saveRow = panel.querySelector('.flex.flex-row.gap-8.items-center.self-center');

    // Kalau ada slider ori, bajak
    const oriSlider = panel.querySelector('input[type="range"][min="50"][max="150"]');
    if (oriSlider) {
      oriSlider.addEventListener('input', function () {
        // map 50-150 ke 1-10
        speed = Math.round((parseInt(this.value) - 50) / 10);
        if (speed < 1) speed = 1;
      });
    }

    // Inject tombol play + delay slider sebelum save button
    const wrapper = document.createElement('div');
    wrapper.id = 'sng-inject-play';
    wrapper.innerHTML = `
      <div class="h-1 w-full bg-base-card"></div>
      <div class="md:text-24 text-20 font-semibold">🚀 Auto Scroll</div>
      ${!oriSlider ? `
      <div class="flex flex-col gap-8">
        <div style="font-size:14px;color:white">Speed: <span id="sng-speed-label">3</span></div>
        <input type="range" min="1" max="10" value="3" id="sng-speed-slider"
          class="w-full rounded-full h-6 accent-primary-500">
        <div style="display:flex;justify-content:space-between;font-size:12px;opacity:0.6;color:white">
          <span>Lambat</span><span>Cepat</span>
        </div>
      </div>
      ` : ''}
      <div class="flex flex-col gap-8">
        <div style="font-size:14px;color:white">Delay mulai: <span id="sng-delay-label">0</span> detik</div>
        <input type="range" min="0" max="10" value="0" id="sng-delay-slider"
          class="w-full rounded-full h-6 accent-primary-500">
        <div style="display:flex;justify-content:space-between;font-size:12px;opacity:0.6;color:white">
          <span>0s</span><span>10s</span>
        </div>
      </div>
      <div class="flex flex-row gap-8 items-center self-center">
        <button id="sng-start-btn"
          class="px-24 py-16 text-16 leading-22 rounded-8 bg-primary-500 text-base-white border-2 border-transparent transition-all duration-200 ease-in-out opacity-100 hover:opacity-80 font-medium min-w-190">
          ▶ Mulai Scroll
        </button>
      </div>
    `;

    const container = panel.querySelector('.flex.flex-col.gap-24.w-full');
    if (!container) return;

    if (saveRow) {
      container.insertBefore(wrapper, saveRow);
    } else {
      container.appendChild(wrapper);
    }

    // Speed slider custom (kalau gak prem)
    const customSpeedSlider = document.getElementById('sng-speed-slider');
    if (customSpeedSlider) {
      customSpeedSlider.addEventListener('input', function () {
        speed = parseInt(this.value);
        document.getElementById('sng-speed-label').textContent = this.value;
      });
    }

    document.getElementById('sng-delay-slider').addEventListener('input', function () {
      document.getElementById('sng-delay-label').textContent = this.value;
    });

    document.getElementById('sng-start-btn').addEventListener('click', () => {
      const delay = parseInt(document.getElementById('sng-delay-slider').value) * 1000;

      // Sync speed dari slider ori kalau ada
      if (oriSlider) {
        speed = Math.max(1, Math.round((parseInt(oriSlider.value) - 50) / 10));
      }

      // Tutup panel pake tombol X ori
      const closeBtn = panel.querySelector('button svg path[d*="10.5867"]')?.closest('button');
      if (closeBtn) closeBtn.click();

      if (delay > 0) {
        setTimeout(startScroll, delay);
      } else {
        startScroll();
      }
    });
  }

  function hijackSettingBtn() {
    const btn = getOriSettingBtn();
    if (!btn || btn.dataset.sngHijacked) return;
    btn.dataset.sngHijacked = 'true';

    btn.addEventListener('click', () => {
      const panelObs = new MutationObserver(() => {
        const panel = getOriPanel();
        if (!panel || document.getElementById('sng-inject-play')) return;
        const container = panel.querySelector('.flex.flex-col.gap-24.w-full');
        if (!container || container.children.length === 0) return;
        panelObs.disconnect();
        injectPlayToPanel();
      });
      panelObs.observe(document.body, { childList: true, subtree: true });
      setTimeout(() => panelObs.disconnect(), 3000);
    }, true);
  }

  function createPauseBtn() {
    if (document.getElementById('sng-pause-btn')) return;
    const pauseBtn = document.createElement('button');
    pauseBtn.id = 'sng-pause-btn';
    pauseBtn.innerHTML = `
      <svg width="22" height="22" viewBox="0 0 40 40" fill="none">
        <rect x="8" y="6" width="8" height="28" rx="2" fill="white"/>
        <rect x="24" y="6" width="8" height="28" rx="2" fill="white"/>
      </svg>`;
    pauseBtn.style.cssText = 'position:fixed!important;bottom:90px!important;left:50%!important;transform:translateX(-50%)!important;z-index:999999!important;background:#6F39EE!important;border:none!important;border-radius:50%!important;width:52px!important;height:52px!important;display:none!important;align-items:center!important;justify-content:center!important;cursor:pointer!important;box-shadow:0 4px 15px rgba(111,57,238,0.7)!important;';
    document.body.appendChild(pauseBtn);
    pauseBtn.addEventListener('click', stopScroll);
  }

  function oriNavbar() {
    return document.querySelector('div.sticky.z-50');
  }

  function showNavbar() {
    navbarVisible = true;
    const nb = oriNavbar();
    if (nb) {
      nb.style.opacity = '1';
      nb.style.transform = 'translateY(0)';
      nb.style.pointerEvents = 'auto';
    }
  }

  function hideNavbar() {
    navbarVisible = false;
    const nb = oriNavbar();
    if (nb) {
      nb.style.opacity = '0';
      nb.style.transform = 'translateY(20px)';
      nb.style.pointerEvents = 'none';
    }
  }

  function startScroll() {
    scrolling = true;
    hideNavbar();
    const pauseBtn = document.getElementById('sng-pause-btn');
    if (pauseBtn) pauseBtn.style.display = 'flex';
    doScroll();
  }

  function stopScroll() {
    scrolling = false;
    cancelAnimationFrame(animFrame);
    const pauseBtn = document.getElementById('sng-pause-btn');
    if (pauseBtn) pauseBtn.style.display = 'none';
    showNavbar();
  }

  function doScroll() {
    if (!scrolling) return;
    window.scrollBy(0, speed);
    if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight - 10) {
      stopScroll();
      return;
    }
    animFrame = requestAnimationFrame(doScroll);
  }

  let tapStartX = 0, tapStartY = 0, tapStartTime = 0;

  document.addEventListener('touchstart', e => {
    const nb = oriNavbar();
    const panel = getOriPanel();
    if (nb?.contains(e.target) || panel?.contains(e.target)) return;
    tapStartX = e.touches[0].clientX;
    tapStartY = e.touches[0].clientY;
    tapStartTime = Date.now();
  }, { passive: true });

  document.addEventListener('touchend', e => {
    const nb = oriNavbar();
    const panel = getOriPanel();
    if (nb?.contains(e.target) || panel?.contains(e.target)) return;
    const dx = Math.abs(e.changedTouches[0].clientX - tapStartX);
    const dy = Math.abs(e.changedTouches[0].clientY - tapStartY);
    const dt = Date.now() - tapStartTime;
    if (dx < 10 && dy < 10 && dt < 250) {
      if (scrolling) { stopScroll(); return; }
      navbarVisible ? hideNavbar() : showNavbar();
    }
  }, { passive: true });

  function init() {
    if (document.getElementById('sng-pause-btn')) return;
    const nb = document.querySelector('div.sticky.z-50');
    if (!nb) return;

    nb.style.transition = 'opacity 0.3s, transform 0.3s';
    createPauseBtn();
    hijackSettingBtn();

    const obs = new MutationObserver(() => {
      hijackSettingBtn();
      const panel = getOriPanel();
      if (panel && panel.offsetParent !== null && !document.getElementById('sng-inject-play')) {
        const container = panel.querySelector('.flex.flex-col.gap-24.w-full');
        if (container && container.children.length > 0) {
          injectPlayToPanel();
        }
      }
    });
    obs.observe(document.body, { childList: true, subtree: true });
  }

  const observer = new MutationObserver(() => {
    const komik = document.querySelector('button.max-w-800 img');
    if (komik) {
      observer.disconnect();
      setTimeout(init, 500);
    }
  });

  observer.observe(document.documentElement, { childList: true, subtree: true });
  setTimeout(init, 3000);
  setTimeout(init, 5000);
})();