Shinigami Auto Scroll

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

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

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.

(I already have a user style manager, let me install it!)

// ==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);
})();