Comick 2+ Chapter Timer Fix

Shows timer for next chapter when 2+ chapters are available

// ==UserScript==
// @name         Comick 2+ Chapter Timer Fix
// @namespace    https://github.com/GooglyBlox
// @version      1.0
// @description  Shows timer for next chapter when 2+ chapters are available
// @author       GooglyBlox
// @match        https://comick.io/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
  'use strict';

  const API_BASE = 'https://api.comick.fun';
  const processed = new WeakSet();
  let timers = new Map();

  function extractSlug(href) {
    const match = href.match(/\/comic\/([^\/\?#]+)/);
    return match?.[1];
  }

  function getCurrentChapter(element) {
    const text = element.textContent || '';
    const match = text.match(/Current\s+(\d+)/);
    return match ? parseInt(match[1], 10) : null;
  }

  async function fetchJSON(url) {
    try {
      const response = await fetch(url);
      return response.ok ? await response.json() : null;
    } catch {
      return null;
    }
  }

  function createTimer(targetTime, element) {
    const timer = document.createElement('div');
    timer.className = 'mt-3 pr-2';
    timer.innerHTML = `
      <a class="btn w-full text-center text-xs px-0 border-none" style="pointer-events: none;">
        <div class="text-orange-600 dark:text-orange-400">
          <p><span class="time">00:00:00</span></p>
        </div>
      </a>
    `;

    const update = () => {
      const diff = targetTime - Date.now();
      if (diff <= 0) {
        timer.innerHTML = `
          <div class="flex items-center h-8">
            <span class="btn w-full text-center text-xs px-0 border-none text-green-600">Available Now</span>
          </div>
        `;
        clearInterval(timers.get(element));
        timers.delete(element);
        return;
      }
      const hours = Math.floor(diff / 3600000);
      const minutes = Math.floor((diff % 3600000) / 60000);
      const seconds = Math.floor((diff % 60000) / 1000);
      timer.querySelector('.time').textContent =
        `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
    };

    update();
    const intervalId = setInterval(update, 1000);
    timers.set(element, intervalId);

    const existingDiv = element.querySelector('.mt-3.pr-2');
    if (existingDiv) {
      existingDiv.replaceWith(timer);
    } else {
      element.appendChild(timer);
    }
  }

  async function processElement(element) {
    if (processed.has(element)) return;
    processed.add(element);

    const currentChapter = getCurrentChapter(element);
    if (!currentChapter) return;

    const comicLink = element.querySelector('a[href*="/comic/"]:not([href*="/chapter/"])');
    if (!comicLink) return;

    const slug = extractSlug(comicLink.href);
    if (!slug) return;

    const comicData = await fetchJSON(`${API_BASE}/comic/${slug}?tachiyomi=true`);
    if (!comicData?.comic) return;

    const chaptersData = await fetchJSON(`${API_BASE}/comic/${comicData.comic.hid}/chapters?lang=en&chap-order=1&limit=300`);
    if (!chaptersData?.chapters) return;

    const chapters = chaptersData.chapters;
    const chapterNumbers = chapters.map(c => parseFloat(c.chap)).filter(n => !isNaN(n));
    const maxChapter = Math.max(...chapterNumbers);

    if (maxChapter - currentChapter < 2) return;

    const now = new Date();
    const upcoming = chapters
      .filter(c => c.publish_at && new Date(c.publish_at) > now)
      .sort((a, b) => new Date(a.publish_at) - new Date(b.publish_at));

    if (upcoming.length > 0) {
      createTimer(new Date(upcoming[0].publish_at), element);
    }
  }

  function scan() {
    const cards = new Set();

    document.querySelectorAll('a[href*="/comic/"]:not([href*="/chapter/"])').forEach(link => {
      const card = link.closest('div');
      if (card && card.textContent.includes('Current')) {
        cards.add(card);
      }
    });

    cards.forEach(card => processElement(card));
  }

  function init() {
    scan();
    new MutationObserver(() => setTimeout(scan, 100)).observe(document.body, {
      childList: true,
      subtree: true
    });
  }

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

  setInterval(scan, 30000);
})();