Exoticaz Search Button Enhancer

Add search buttons to Exoticaz torrent list and detail pages

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Exoticaz Search Button Enhancer
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Add search buttons to Exoticaz torrent list and detail pages
// @match        https://exoticaz.to/torrents*
// @match        https://exoticaz.to/torrent/*
// @grant        none
// @license      MIT
// ==/UserScript==
(function () {
  'use strict';

  // Configuration: Add or modify search providers here
  const SEARCH_PROVIDERS = [
    {
      name: 'NetFlav',
      icon: '',
      url: 'https://netflav.com/search?type=title&keyword=',
      className: 'btn-warning'
    },
    {
      name: 'JavMost',
      icon: '',
      url: 'https://www5.javmost.com/search/',
      className: 'btn-warning'
    },
    // Add more providers here:
    // {
    //   name: 'Google',
    //   icon: '🌐',
    //   url: 'https://www.google.com/search?q=',
    //   className: 'btn-info'
    // }
  ];

  function createSearchButton(code, provider, isListPage = false) {
    const btn = document.createElement(isListPage ? 'button' : 'a');
    btn.textContent = `${provider.icon} ${provider.name}`;
    btn.className = `btn btn-xs ${provider.className} search-btn`;
    btn.style.marginLeft = '6px';
    btn.style.marginTop = isListPage ? '4px' : '0';

    if (isListPage) {
      // For list page buttons
      btn.style.padding = '2px 6px';
      btn.style.fontSize = '12px';
      btn.style.border = '1px solid #ccc';
      btn.style.borderRadius = '4px';
      btn.style.cursor = 'pointer';
      btn.onclick = (e) => {
        e.stopPropagation();
        window.open(`${provider.url}${encodeURIComponent(code)}`, '_blank');
      };
    } else {
      // For detail page buttons (links)
      btn.href = `${provider.url}${encodeURIComponent(code)}`;
      btn.target = '_blank';
    }

    return btn;
  }

  function addSearchButtons(container, code, isListPage = false) {
    // Remove existing search buttons to avoid duplicates
    container.querySelectorAll('.search-btn').forEach(btn => btn.remove());

    // Add all configured search buttons
    SEARCH_PROVIDERS.forEach(provider => {
      const searchBtn = createSearchButton(code, provider, isListPage);
      container.appendChild(searchBtn);
    });
  }

  function handleDetailPage() {
    const titleEl = document.querySelector('h1.h4');
    if (!titleEl) return;

    const match = titleEl.textContent.match(/\[([^\]]+)\]/);
    if (!match) return;

    const code = match[1];

    // Look for the "Download as Text File" button
    const txtBtn = Array.from(document.querySelectorAll('a.btn'))
      .find(el => el.textContent.includes('Download as Text'));

    if (!txtBtn) return;

    const container = txtBtn.parentElement;
    if (!container) return;

    // Create a wrapper for search buttons if it doesn't exist
    let searchWrapper = container.querySelector('.search-wrapper');
    if (!searchWrapper) {
      searchWrapper = document.createElement('span');
      searchWrapper.className = 'search-wrapper';
      container.insertBefore(searchWrapper, txtBtn.nextSibling);
    }

    addSearchButtons(searchWrapper, code, false);
  }

  function handleListPage() {
    // Use requestAnimationFrame to avoid blocking the main thread
    const processInBatches = () => {
      const torrentLinks = document.querySelectorAll('a.torrent-link:not([data-search-processed])');
      const batchSize = 10; // Process 10 items at a time

      for (let i = 0; i < Math.min(batchSize, torrentLinks.length); i++) {
        const link = torrentLinks[i];
        link.setAttribute('data-search-processed', 'true');

        const title = link.getAttribute('title') || link.textContent;
        const match = title.match(/\[([^\]]+)\]/);
        if (!match) continue;

        const code = match[1];
        const row = link.closest('tr');
        if (!row) continue;

        const actionTd = row.querySelector('td > .align-top')?.parentElement;
        const alignBottom = actionTd?.querySelector('.align-bottom');
        if (!alignBottom) continue;

        // Create a wrapper for search buttons if it doesn't exist
        let searchWrapper = alignBottom.querySelector('.search-wrapper');
        if (!searchWrapper) {
          searchWrapper = document.createElement('div');
          searchWrapper.className = 'search-wrapper';
          searchWrapper.style.marginTop = '4px';
          alignBottom.appendChild(searchWrapper);
        }

        addSearchButtons(searchWrapper, code, true);
      }

      // If there are more items to process, schedule the next batch
      if (torrentLinks.length > batchSize) {
        requestAnimationFrame(processInBatches);
      }
    };

    requestAnimationFrame(processInBatches);
  }

  function init() {
    const isDetailPage = /https:\/\/exoticaz\.to\/torrent\/\d+/.test(location.href);
    if (isDetailPage) {
      handleDetailPage();
    } else {
      handleListPage();

      // Throttled observer to prevent excessive function calls
      let observerTimeout;
      const throttledHandleListPage = () => {
        clearTimeout(observerTimeout);
        observerTimeout = setTimeout(handleListPage, 250); // Wait 250ms before processing
      };

      const observer = new MutationObserver(throttledHandleListPage);
      observer.observe(document.body, {
        childList: true,
        subtree: true,
        // Only observe specific changes to reduce overhead
        attributeFilter: ['class', 'data-search-processed']
      });
    }
  }

  // Use both DOMContentLoaded and load events for better reliability
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => {
      // Small delay to ensure page is fully rendered
      setTimeout(init, 100);
    });
  } else {
    setTimeout(init, 100);
  }

  // Backup initialization on window load
  window.addEventListener('load', () => {
    setTimeout(init, 200);
  });
})();