RARBG Assistant

Userscript for RARBG.to to make it more user friendly.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         RARBG Assistant
// @namespace    https://github.com/PoLaKoSz
// @version      1.8.3
// @description  Userscript for RARBG.to to make it more user friendly.
// @author       Tom PoLáKoSz
// @grant        none
// @icon         https://raw.githubusercontent.com/PoLaKoSz/RARBG-Assistant/master/assets/icon.png
// @run-at       document-start
// @include      https://rarbg.to/trailers.php*
// ==/UserScript==

'use strict';

class KeyDetector {
  isEscPressed(event) {
    event = event || window.event;
    if ("key" in event) {
        return event.key === "Escape" || event.key === "Esc";
    }

    return event.keyCode === 27;
  }
}

class RarbgAssistant {
  constructor() {
    this.requestCatcher = new XMLHttpRequestCatcher();
    this.trailersPage = new TrailersPage();
  }

  initalize() {
    this.requestCatcher.register("/ajax.php?mode=trailers&mode2=list", this.trailersPage);

    document.addEventListener('DOMContentLoaded', function() {
      const trailerWindow = new TrailerWindow();
      trailerWindow.float();
      trailerWindow.onOpen([
        {
          instance: trailerWindow,
          method: trailerWindow.addSearchButton,
        },
        {
          instance: trailerWindow,
          method: trailerWindow.fixOverlayForAdblockers,
        },
        {
          instance: trailerWindow,
          method: trailerWindow.hideScrollbar,
        },
      ]);
      trailerWindow.onClose([
        {
          instance: trailerWindow,
          method: trailerWindow.showScrollbar,
        },
      ]);

      document.onkeydown = function(event) {
        const keyDetector = new KeyDetector();
        if (keyDetector.isEscPressed(event)) {
          trailerWindow.close();
        }
      };
    });
  }
}

class TrailersPage {
  invoke(payload) {
    const trailerIds = payload.match(/(?<=id="trailer_)(\d+)/g);
    if (!trailerIds) {
      const message = 'Couldn\'t find any trailer Id!';
      console.log(message, { payload: payload });
      throw message;
    }

    trailerIds.forEach(id => {
      let trailerContainerNode = document.querySelector(`#trailer_${id}`);
      const anchors = trailerContainerNode.querySelectorAll("a");
      anchors.forEach(anchor => {
        let proxiedClick = anchor.onclick;
        anchor.onclick = function() {
          proxiedClick();
          return false;
        }
      })
    });
  }
}

class TrailerWindow {
  constructor() {
    this.trailerModalNode = document.querySelector("#trailer_modal");
    this.openEventSubscribers = [];
    this.closeEventSubscribers = [];
  }

  float() {
    document.body.appendChild(this.trailerModalNode);
    this.trailerModalNode.classList.add("trailer-modal");
  }

  close() {
    this.getCloseFunction()("#");
  }

  onOpen(actions) {
    this.openEventSubscribers = actions;
    this.attachEventToWindowOpen();
  }

  addSearchButton(self) {
    const titleContainerNode = self.trailerModalNode.querySelector("table:nth-child(3) > tbody > tr > td:nth-child(1)");
    const titleNode = titleContainerNode.querySelector("b");
    const button = `
      <span>
        <a href="https://rarbg.to/torrents.php?search=${titleNode.innerText}" target="_blank">
          <i class="icon-search"></i>
          Search for ${titleNode.innerText}
        </a>
      </span>
      `;
    titleContainerNode.innerHTML += button;
  }

  attachEventToWindowOpen() {
    const proxied = jQuery.fn.jqm;
    const eventSubscribers = this.openEventSubscribers;
    jQuery.fn.jqm = function() {
      arguments[0]['onLoad'] = function() {
        eventSubscribers.forEach(subscriber => {
          subscriber.method(subscriber.instance);
        });
      }

      return proxied.apply(this, arguments);
    };
  }

  fixOverlayForAdblockers() {
    const overlayNode = document.querySelector(".jqmOverlay");
    overlayNode.classList.remove("jqmOverlay");
    overlayNode.setAttribute("id", "jqmOverlay");
    overlayNode.style.backgroundColor = "#000";
    const instance = this.instance;
    overlayNode.onclick = function() {
      instance.close();
    }
  }

  hideScrollbar() {
    const originalWidth = document.body.clientWidth;
    document.body.style.overflow = "hidden";
    document.body.style.width = `${originalWidth}px`;
  }

  showScrollbar() {
    document.body.style.overflow = "visible";
    document.body.style.width = "auto";
  }

  onClose(actions) {
    this.closeEventSubscribers = actions;
    this.attachEventToWindowClose();
  }
  
  attachEventToWindowClose() {
    const eventSubscribers = this.closeEventSubscribers;
    const proxied = this.getCloseFunction();
    window.closejqm = function() {
      eventSubscribers.forEach(subscriber => {
        subscriber.method(subscriber.instance);
      });

      return proxied.apply(this, arguments);
    };
  }

  getCloseFunction() {
    const closeFunction = window.closejqm;
    if (closeFunction === undefined) {
      throw "RARBG developers removed closejqm global function!";
    }

    return closeFunction;
  }
}

class XMLHttpRequestCatcher {
  constructor() {
    const proxied = window.XMLHttpRequest.prototype.send;
    const requestCatcher = this;
    window.XMLHttpRequest.prototype.send = function() {
      const request = this;
      const intervalId = window.setInterval(function() {
        if(request.readyState != 4) {
          return;
        }

        requestCatcher.invokeActionIfExists(request);
        clearInterval(intervalId);
      }, 1);
      return proxied.apply(this, [].slice.call(arguments));
    };
    this.lookupTable = {};
  }

  register(url, action) {
    url = new URL(`https://rarbg.to${url}`);
    this.lookupTable[url] = action;
  }

  invokeActionIfExists(request) {
    // sometimes responseURL is empty
    if (!request.responseURL) {
      return;
    }

    const requestURL = new URL(request.responseURL);
    Object.keys(this.lookupTable).forEach(key => {
      const registeredURL = new URL(key);
      if (registeredURL.pathname !== requestURL.pathname) {
        return;
      }

      const registeredSearchParams = Array.from(registeredURL.searchParams.keys());
      const requestSearchParams = Array.from(requestURL.searchParams.keys());
      if (registeredSearchParams.length > requestSearchParams.length) {
        return;
      }

      let matchCount = 0;
      registeredSearchParams.forEach(registeredSearchParam => {
        matchCount += registeredURL.searchParams.get(registeredSearchParam) === requestURL.searchParams.get(registeredSearchParam);
      });
      if (matchCount != registeredSearchParams.length) {
        return;
      }

      this.lookupTable[key].invoke(request.responseText);
    });
  }
}

const rarbgAssistant = new RarbgAssistant();
rarbgAssistant.initalize();


const styleNode = document.createElement('style');
styleNode.type = 'text/css';
styleNode.innerText = '.trailer-modal{position:fixed!important}';
document.head.appendChild(styleNode);