Greasy Fork

copyTorrentToRelated

Copy current torrent row to Related Torrents section

// ==UserScript==
// @name        copyTorrentToRelated
// @namespace   https://avistaz.to/profile/dryeyes
// @description Copy current torrent row to Related Torrents section
// @match       *://cinemaz.to/torrent/*
// @match       *://avistaz.to/torrent/*
// @match       *://privatehd.to/torrent/*
// @version     0.9.8
// @grant       none
// @locale      English (en)
// ==/UserScript==
(function(){
  'use strict';

  function parseHTML (str) {
    let tmp = document.implementation.createHTMLDocument();
    tmp.body.innerHTML = str;
    if (tmp.body.children.length === 1) {
      return tmp.body.children[0];      
    } else {
      return tmp.body.children;      
    }
  }

  function injectFunc(fn) {
    var scriptElm = document.createElement('script');
    scriptElm.setAttribute("type", "application/javascript");
    scriptElm.textContent = '(' + fn + ')();';
    //scriptElm.async = false;  didn't help.
    document.body.appendChild(scriptElm); // run the script
    document.body.removeChild(scriptElm); // clean up
  }

  function getNoRelatedTorrentsNode () {
    var noTorrentsFoundNode = document.evaluate('//div[@id="collapseRelatedTorrents"]/div[@class="related-torrents"]/p[normalize-space()="No torrents found!"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    return noTorrentsFoundNode;
  }

  function getTableDataNode(tableCaption) {
    var xpathexp = `//tr/td[strong[contains(text(),'${tableCaption}')]]/following-sibling::td`;
    var tableDataNode = document.evaluate(xpathexp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    return tableDataNode;
  }

  var navbar;

  // takes into account current navbar height
  function scrollIDToTop(ID, evt) {
    evt.preventDefault(); // THIS stops browser from scrolling to top, but also blocks history.

    if (navbar !== undefined) {
      var newUrl = "#" + ID;
      history.pushState(null, ID, newUrl); // manually save position in history
      var elm = document.querySelector(newUrl);
      elm.scrollIntoView(true);
      window.scrollBy(0,-(navbar.offsetHeight + 10));
    }
    return false; // supposed to block browser from scrolling to top of page but doesn't work.
  }

  var cleanedUrl = window.location.href.replace(/(#.*)$/, '');
  var newTR;    // the copied <tr> element from related torrents query.
  var nRelated; // # of Related Torrents (including current)
  var atLeast;  // "+" if more than one page of torrents otherwise ""

  function addOnClickHandler(node,ID) {
    node.addEventListener("click", function(evt){scrollIDToTop(ID, evt); }, false);
  }

  function appendIconNode(grandParent, parent, ID) {
    var aNode = parent.querySelector("a");
    addOnClickHandler(aNode, ID);
    grandParent.appendChild(parent);
  }

  function addExtraInfo() {
    if (newTR === null)
      return;

    //Make "Sub:" text be link to Subtitles row
    var xpathexp = "//td/div/div/strong[contains(text(),'Sub:')]";
    var subNode = newTR.ownerDocument.evaluate(xpathexp, newTR, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

    // dummy node creation to force jQuery.jstree.reference() to work in Chrome?
    var newSubNode = document.createElement('a'); 
    if (subNode !== null) {
      newSubNode = parseHTML('<a href="#"><strong>Sub:</strong></a>');
      addOnClickHandler(newSubNode, "subtitles-row");
      subNode.parentNode.replaceChild(newSubNode, subNode);
    }

    //Make "Thanked" icon be link Thanked row
    xpathexp = "//td/div/span/i[@title='Thanked']";
    subNode = newTR.ownerDocument.evaluate(xpathexp, newTR, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (subNode !== null) {
      newSubNode = parseHTML('<a href="#"></a>');
      var clonedSubNode = subNode.cloneNode(true);
      newSubNode.appendChild(clonedSubNode);
      addOnClickHandler(newSubNode, "thanked-row");
      subNode.parentNode.replaceChild(newSubNode, subNode);
    }

    var vidResolutionNode = getTableDataNode('Video Resolution');
    if (vidResolutionNode !== null) {
      var vidRes = vidResolutionNode.textContent.trim().toLowerCase();
      console.log("Video Resolution:", vidRes);
      if (vidRes !== 'unknown') {
        var vidResSpan = parseHTML(`<span class="badge-extra">${vidRes}</span>`);
        var tfileDiv = newTR.querySelector('td:nth-of-type(2) > div > div:nth-of-type(2)');
        if (tfileDiv !== null) {
          console.log("Adding vidResSpan:", vidResSpan);
          tfileDiv.appendChild(vidResSpan);
        }
      }
    }

    var tFileDivNode = newTR.querySelector('div.torrent-file');

    var fileTree = jQuery.jstree.reference('div#file_list_tree').get_json('#', { flat: true });
    console.log("fileTree length:", fileTree.length);
    var nFiles = 0;
    var noMatch = 0;
    for (var i = 0, j = fileTree.length; i < j; i++) {
      if (fileTree[i].icon !== undefined) {
        var icon = fileTree[i].icon;
        if (!icon.includes("fa-folder")) {
          nFiles += 1;
        } else {
          noMatch += 1;
          //console.log("not folder icon:", noMatch, icon);
        }
      }
    }

    var filesSpan = parseHTML(`<span class="badge-extra"><a href="#"><i class="icon-like fa fa-sitemap" data-toggle="tooltip" title="Files"></i> ${nFiles}</a></span>`);
    appendIconNode(tFileDivNode, filesSpan, "files-row");

    var descriptionNode = getTableDataNode('Description');
    var descriptionLength = "0";
    var tlen = descriptionNode.innerText.length;
    if (descriptionNode !== null && tlen !== 0) {
      descriptionLength = (tlen / 1024.0).toFixed(1);
    }

    var descriptionSpan = parseHTML(`<span class="badge-extra"><a href="#"><i class="icon-like fa fa-file-text" data-toggle="tooltip" title="Description"></i> ${descriptionLength}K</a></span>`);
    appendIconNode(tFileDivNode, descriptionSpan, "description-row");

    var screenShotsSmallNode = document.evaluate('//div[@data-target="#collapseScreens"]/small', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    var nScreens = 0;
    if (screenShotsSmallNode !== null) {
      console.log("Screens:", screenShotsSmallNode);
      nScreens = screenShotsSmallNode.textContent.match(/(\d+)\s+image?/i)[1];
    }
    var screenSpan = parseHTML(`<span class="badge-extra"><a href="#"><i class="icon-like fa fa-picture-o" data-toggle="tooltip" title="Screenshots"></i> ${nScreens}</a></span>`);
    appendIconNode(tFileDivNode, screenSpan, "screenshots-panel");

    var comments = document.querySelectorAll('div#commentsBlock div.comments > ul li');
    console.log("Comments:", comments);
    var commentsSpan = parseHTML(`<span class="badge-extra"><a href="#"><i class="icon-like fa fa-comment" data-toggle="tooltip" title="Comments"></i> ${comments.length}</a></span>`);
    appendIconNode(tFileDivNode, commentsSpan, "comments-panel");

    var tagsNode = getTableDataNode('Tags');
    if (tagsNode !== null) {
      var tagsDiv = document.createElement('div');
      var arefs = tagsNode.querySelectorAll('a');
      arefs.forEach(function(item) {
        var tagSpan = parseHTML(`<span class="badge-extra"><i class="fa fa-tag"></i> <a href="${item.href}" title="${item.title}">${item.textContent}</a></span>`);
        tagsDiv.appendChild(tagSpan);
      });
      tFileDivNode.appendChild(tagsDiv);
    }
  }

  // XMLHttpRequest loaded event handler of related torrents rows
  function onRelatedLoaded () {
    var i, j;

    console.log("XMLHttpRequest loaded.");
    var relatedRow = this.response;
    console.log(relatedRow);

    var relatedRows = relatedRow.querySelectorAll("div.table-responsive tbody > tr");
    console.log("Main page torrent rows:", relatedRows);
    console.log("Looking for:", cleanedUrl);

    for (i = 0, j = relatedRows.length; i < j; i++) {
      var row = relatedRows[i];
      var anode = row.querySelector("a.torrent-filename");
      if (anode !== null) {
        console.log("Anode:", anode);
        if (anode.href === cleanedUrl) {
          newTR = row.cloneNode(true);

          var tdNode = newTR.querySelector("td:last-of-type");
          var inode = parseHTML(`<i class="fa fa-hand-o-left text-red" title="Current Torrent"></i>`);
          tdNode.appendChild(inode);
          console.log("ClonedTR:", newTR);
          break;
        }
      }
    }

    var lastPageLI = relatedRow.querySelector("ul.pagination li:nth-last-of-type(2)");
    if (lastPageLI !== null) {
      var nFullPages = Number(lastPageLI.textContent) - 1;
      nRelated = (relatedRows.length*nFullPages);
      atLeast = "+";
    } else {
      nRelated = relatedRows.length;
      atLeast = "";
    }
    console.log ("nTorrents:",nRelated,atLeast);
    addExtraInfo();
  }

  // Download & copy matching torrent row from related torrents url
  function copyRelatedRow() {
    var mainURL = document.querySelector("h3.movie-title > a").href;
    var pieces = mainURL.split("/");
    var site = pieces[2];
    var torrentID = pieces[4].split("-")[0];
    var relatedURL = `https://${site}/movies/torrents/${torrentID}?quality=all`;

    var xhr = new XMLHttpRequest();
    xhr.onload = onRelatedLoaded;
    xhr.open("GET", relatedURL);
    xhr.responseType = "document";
    xhr.send();

    console.log("XMLHttpRequest for "+relatedURL+ " sent.");
  }

  var relatedAdded = false;

  function relatedShown () {
    if (relatedAdded)
      return;
    else
      relatedAdded = true;

    console.log("Related shown.", document.readyState);

    var relatedTorrent = document.querySelector("#collapseRelatedTorrents > div.related-torrents > div > table > tbody > tr");

    console.log("Related torrent:", relatedTorrent);
    var noRelatedTorrentsNode = getNoRelatedTorrentsNode();
    if (noRelatedTorrentsNode !== null)
      console.log("After expanded: No related torrents message found.");

    if (relatedTorrent !== null) {
      if (newTR !== undefined) {
        console.log("NewTR:");
        console.log(newTR);

        relatedTorrent.parentNode.insertBefore(newTR, relatedTorrent);
      }
    } else {
      if (noRelatedTorrentsNode !== null) {
        var divElm = parseHTML(`
<div class="related-torrents">
 <div class="table-responsive">
  <div class="pull-right"></div>
   <table class="table table-condensed table-striped table-bordered">
    <thead>
    <tr>
    <th class="torrents-icon"></th>
    <th class="torrents-filename">File</th>
    <th><i class="fa fa-download"></i></th>
    <th>Age</th>
    <th>Size</th>
    <th>S</th>
    <th>L</th>
    <th>C</th>
    </tr>
    </thead>
    <tbody>
    </tbody>
   </table>
  <div class="pull-right"></div>
 </div>
</div>
`);
        console.log("About to create new related.");
        var tbody = divElm.querySelector("tbody");
        if (newTR !== undefined) {
          console.log("NewTR:");
          console.log(newTR);
          tbody.appendChild(newTR);
        }

        var grandParent = noRelatedTorrentsNode.parentNode.parentNode;
        grandParent.replaceChild(divElm, noRelatedTorrentsNode.parentNode);
      } else {
        console.log("Ooops. No related torrents AND couldn't find the No Related Torrents message?", document.readyState);
        window.alert("Ooops. No related torrents AND couldn't find the No Related Torrents message?\n\nKeep refreshing page until this alert doesn't appear.");
      }
    }

    var relatedTorrents = document.querySelectorAll("#collapseRelatedTorrents > div.related-torrents > div > table > tbody > tr");
    console.log("relatedTorrents:", relatedTorrents);
    var relatedTorrentsA = document.querySelector('div.panel-heading > strong > a');
    if (relatedTorrentsA !== null) {
      if (nRelated === null) {
        var nTorrents = relatedTorrents.length > 0 ? relatedTorrents.length : 1;
        relatedTorrentsA.textContent += " [" + nTorrents + "]";
      } else {
        relatedTorrentsA.textContent += " [" + nRelated + atLeast + "]";
      }
    }
  }

  // Expose relatedShown() to injected func.
  window.relatedShown = relatedShown; // doesn't work with Greasemonkey 4.1
  //exportFunction(relatedShown, window, {defineAs: "relatedShown"}); // doesn't work in Greasemonkey 3.17, get exportFunction not defined error :(

  // Use script injection so can use jQuery to listen to shown.bs.collapse event which
  //  will be automatically triggered when we click the Related Torrents panel during
  //  load event processing.
  function injectedFunc() {
    console.log("Injected function running");
    var relatedTorrentsDiv = document.querySelector('div#collapseRelatedTorrents');
    console.log ("relatedTorrentsDiv:", relatedTorrentsDiv);

    $(relatedTorrentsDiv).on('shown.bs.collapse', relatedShown); // also works!
  }

  function clickRelated() {
    console.log("Click Related Torrents header.");
    var relatedTorrentsA = document.querySelector('div.panel-heading > strong > a');
    if (relatedTorrentsA !== null) {
      relatedTorrentsA.click();
    }
  }

  function markTableRow(dataName, dataID) {
    var dataNode = getTableDataNode(dataName);
    if (dataNode !== null) {
      dataNode.parentNode.id = dataID;
    }
  }

  function onLoadHandler() {
    console.log("copyTorrentToRelated Load event occurred:", cleanedUrl);

    navbar = document.querySelector("div.container");

    markTableRow('Files', "files-row");
    markTableRow('Description', "description-row");
    markTableRow('Subtitles', "subtitles-row");
    markTableRow('Thanked', "thanked-row");

    var panelNode = document.evaluate('//div[@data-target="#collapseScreens"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (panelNode !== null)
      panelNode.id = "screenshots-panel";
    panelNode = document.evaluate('//div[@data-target="#collapseComments"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (panelNode !== null)
      panelNode.id = "comments-panel";

    if (newTR !== undefined) {
      clickRelated();
    } else {
      console.log("XMLHttpRequest not done. Wait 2 seconds to click.");
      setTimeout(clickRelated, 2000);
    }
  }
  console.log("UserScript running");
  copyRelatedRow();

  injectFunc(injectedFunc);

  window.addEventListener('load', onLoadHandler, false);
})();