RARBG Assistant

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

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