- // ==UserScript==
- // @name rarbg-magnet-batch-copy
- // @namespace http://tampermonkey.net/
- // @version 0.1
- // @description Display checkboxes and magnet icons on rarbg search result page, you can batch copy magnet links.
- // @author Xavier Lee
- // @match *://rarbg.to/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=rarbg.to
- // @grant none
- // ==/UserScript==
-
- (function () {
- "use strict";
- /**
- * Global constant
- */
- const itemCheckboxClass = "rarbg-magnet-batch-item-checkbox";
- const modalWrapperId = "rarbg-magnet-batch-modal-wrapper";
- const modalContentId = "rarbg-magnet-batch-modal-content";
- const selectAllText = "✅Select All";
- const unselectAllText = "🔳Unselect All";
- const copySelectedButtonText = "🧲📋Copy Selected Magnet Links";
- // selectors on the rarbg details page
- const rarbgPageMagnetIconSelector = "a[href^='magnet:?']";
- const rarbgPageTorrentLinkSelector = "a[href$='.torrent']";
- // selectors on the rarbg search result page
- const itemRowSecondTdSelector = "tr.lista2 > td:nth-child(2)";
- const titleLinkInSecondTdSelector = "a[title]";
- const headerRowSecondCellSelector =
- "table.lista2t > tbody > tr:nth-child(1) > td:nth-child(2)";
- const itemRowSelector = "table.lista2t > tbody > tr.lista2";
-
- /**
- * Create control elements
- */
- const createControlElement = function (controlType) {
- const controlDetailsObject = {
- selectAllButton: {
- element: "button",
- innerText: selectAllText,
- attributes: {
- style: "margin-right:10px;",
- },
- },
- copySelectedMagnetButton: {
- element: "button",
- innerText: copySelectedButtonText,
- attributes: {
- style: "margin-left:10px;",
- },
- },
- lineBreak: {
- element: "br",
- },
- itemCheckbox: {
- element: "input",
- attributes: {
- type: "checkbox",
- style: "height: 12px;",
- class: itemCheckboxClass,
- },
- },
- magnetIcon: {
- element: "img",
- attributes: {
- title: "copy magnet link",
- class: "copyManet",
- src: "https://dyncdn.me/static/20/img/magnet.gif",
- style: "margin: 0px 5px;",
- },
- },
- torrentIcon: {
- element: "img",
- attributes: {
- title: "download torrent file",
- class: "downloadTorrent",
- src: "https://dyncdn.me/static/20/img/16x16/download.png",
- },
- },
- modalWrapper: {
- element: "div",
- innerText: "",
- attributes: {
- id: modalWrapperId,
- style:
- "display:none; position:fixed; z-index:1; padding-top:100px; left:0; top:0; width:100%; height:100%; overflow:auto; background-color:rgba(0,0,0,0.4);",
- },
- },
- modalContent: {
- element: "div",
- innerText: "",
- attributes: {
- id: modalContentId,
- style:
- "background-color:#fefefe; margin:auto; padding:20px; border:1px solid #888; width:80%; color:#000;",
- },
- },
- };
- const possibleTypes = Object.keys(controlDetailsObject);
- if (!possibleTypes.includes(controlType)) {
- throw `createControlElement function, argument controlType is ${controlType}, expecting ${possibleTypes}`;
- }
- const controlDetail = controlDetailsObject[controlType];
- const controlAttributes = controlDetail.attributes;
- const controlElement = document.createElement(controlDetail.element);
- controlElement.innerText = controlDetail.innerText;
- for (const attributeName in controlAttributes) {
- if (Object.hasOwnProperty.call(controlAttributes, attributeName)) {
- const attributeValue = controlAttributes[attributeName];
- controlElement.setAttribute(attributeName, attributeValue);
- }
- }
- return controlElement;
- };
-
- /**
- * This function UPDATE the array of rarbgMagnetTorrentItems
- * rarbgMagnetTorrentItems should be something like:
- [
- {
- name: "This.Is.Us.S04E18.1080p.AMZN.WEBRip.DDP5.1.x264-KiNGS[rartv]",
- pageUrl: "https://rarbg.to/torrent/dti6lc1",
- magnetUrl: null,
- torrentUrl: null,
- },
- {
- name: "This.Is.Us.S04E17.1080p.AMZN.WEBRip.DDP5.1.x264-KiNGS[rartv]",
- pageUrl: "https://rarbg.to/torrent/pnljgd2",
- magnetUrl: null,
- torrentUrl: null,
- },
- ]
- * Then the function should update the magnetUrl and torrentUrl in each object of the array
- */
- const updateRarbgMagnetTorrentItems = async function (items) {
- const responses = await Promise.all(
- items.map((item) => fetch(item.pageUrl))
- );
- const htmls = await Promise.all(responses.map((r) => r.text()));
- htmls.forEach((html) => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, "text/html");
- const torrentLink = doc.querySelector(rarbgPageTorrentLinkSelector);
- const magnetIcon = doc.querySelector(rarbgPageMagnetIconSelector);
- const pageName = torrentLink.innerText;
- const magnetUrl = magnetIcon.getAttribute("href");
- const torrentUrl = torrentLink.getAttribute("href");
- const currentName = items.find((item) => item.name === pageName);
- currentName.magnetUrl = magnetUrl;
- currentName.torrentUrl = torrentUrl;
- });
- return items;
- };
-
- /**
- * This function GENERATE one rarbgMagnetTorrentItem obj from the second td in an item row
- */
- const generateRarbgMagnetTorrentItem = function (td) {
- const titleLink = td.querySelector(titleLinkInSecondTdSelector);
- return {
- name: titleLink.innerText,
- pageUrl: titleLink.getAttribute("href"),
- magnetUrl: null,
- torrentUrl: null,
- };
- };
-
- /**
- * This function GENERATE the array of rarbgMagnetTorrentItems for selected
- * Or for a single item in an array when click the icon directly
- */
- const generateRarbgMagnetTorrentItemsForSelected = function (
- targetTd = null
- ) {
- if (targetTd) {
- return [generateRarbgMagnetTorrentItem(targetTd)];
- }
- const itemRowSecondTds = document.querySelectorAll(itemRowSecondTdSelector);
- return Array.from(itemRowSecondTds)
- .filter((td) => td.querySelector(`.${itemCheckboxClass}`).checked)
- .map((td) => generateRarbgMagnetTorrentItem(td));
- };
-
- /**
- * Select or Un-select all item checkboxes
- */
- const selectOrUnselectAllItems = function (event) {
- const targetButton = event.target;
- const currentButtonText = targetButton.innerText;
- const checkboxes = document.querySelectorAll(`.${itemCheckboxClass}`);
- if (currentButtonText === selectAllText) {
- checkboxes.forEach((checkbox) => (checkbox.checked = true));
- targetButton.innerText = unselectAllText;
- } else {
- checkboxes.forEach((checkbox) => (checkbox.checked = false));
- targetButton.innerText = selectAllText;
- }
- };
-
- /**
- * This function showing a message to user
- */
-
- const showMessage = function (str) {
- const modalWrapper = document.getElementById(modalWrapperId);
- const modalContent = document.getElementById(modalContentId);
- modalContent.innerText = str;
- modalWrapper.style.display = "block";
-
- setTimeout(() => {
- modalWrapper.style.display = "none";
- modalContent.innerText = "";
- }, 2000);
- };
-
- /**
- * This function get UPDATED rarbgMagnetTorrentItems for selected or targeted
- */
- const getUpdatedRarbgMagnetTorrentItems = async function (event) {
- let targetTdOrNull;
- if (event.target.tagName !== "BUTTON") {
- // means user clicked icon
- targetTdOrNull = event.target.parentNode;
- }
- const rarbgMagnetTorrentItems =
- generateRarbgMagnetTorrentItemsForSelected(targetTdOrNull);
- await updateRarbgMagnetTorrentItems(rarbgMagnetTorrentItems);
- return rarbgMagnetTorrentItems;
- };
-
- /**
- * This function copy selected items' magnet urls to clipboard
- */
- const copySelectedMagnetUrls = async function (event) {
- const rarbgMagnetTorrentItems = await getUpdatedRarbgMagnetTorrentItems(
- event
- );
- const textToCopy = rarbgMagnetTorrentItems
- .map((item) => item.magnetUrl)
- .join("\t\n");
- await navigator.clipboard.writeText(textToCopy);
- showMessage(
- `successfully copied ${rarbgMagnetTorrentItems.length} links to clipboard:
- ${textToCopy}`
- );
- };
-
- /**
- * This function download the torrent file
- */
- const downloadSelectedTorrent = async function (event) {
- const rarbgMagnetTorrentItems = await getUpdatedRarbgMagnetTorrentItems(
- event
- );
- const torrentUrl = rarbgMagnetTorrentItems[0].torrentUrl;
- const name = rarbgMagnetTorrentItems[0].name;
- const link = document.createElement("a");
- link.href = torrentUrl;
- link.setAttribute("target", "_blank");
- link.click();
- };
-
- /**
- * Insert UI buttons on top of the table
- */
- const insertUiButtonsOnTop = function (target = null) {
- const scopeDoc = target ? target : document;
- const headerRowSecondCell = scopeDoc.querySelector(
- headerRowSecondCellSelector
- );
- if (!headerRowSecondCell) {
- return;
- }
- const selectAllButton = createControlElement("selectAllButton");
- selectAllButton.addEventListener("click", selectOrUnselectAllItems);
- const copySelectedMagnetButton = createControlElement(
- "copySelectedMagnetButton"
- );
- copySelectedMagnetButton.addEventListener("click", copySelectedMagnetUrls);
- headerRowSecondCell.append(selectAllButton, copySelectedMagnetButton);
- };
-
- /**
- * Insert UI controls into each item row
- */
- const insertUiControlElementsPerRow = function (target = null) {
- const scopeDoc = target ? target : document;
- const itemRows = scopeDoc.querySelectorAll(itemRowSelector);
-
- itemRows.forEach((row, i) => {
- const itemLinkTd = row.querySelectorAll("td")[1];
- const itemCheckbox = createControlElement("itemCheckbox");
- const magnetIcon = createControlElement("magnetIcon");
- magnetIcon.addEventListener("click", copySelectedMagnetUrls);
- const torrentIcon = createControlElement("torrentIcon");
- torrentIcon.addEventListener("click", downloadSelectedTorrent);
- const lineBreak = createControlElement("lineBreak");
- itemLinkTd.prepend(itemCheckbox, magnetIcon, torrentIcon, lineBreak);
- });
- };
-
- /**
- * Insert model related element for notification
- */
- const insertModelToBody = function () {
- const modalWrapper = createControlElement("modalWrapper");
- const modalContent = createControlElement("modalContent");
- modalWrapper.append(modalContent);
- document.body.append(modalWrapper);
- };
-
- /**
- * Setting up the observer when click expanding on tv page
- */
- const setupObserverForTvPage = function () {
- const contentTable = document.querySelector(".lista-rounded");
- const observer = new MutationObserver(function (mutations) {
- mutations.forEach((mutation) => {
- if (mutation.removedNodes.length > 0) {
- insertUiButtonsOnTop(mutation.target);
- insertUiControlElementsPerRow(mutation.target);
- }
- });
- });
- observer.observe(contentTable, { childList: true, subtree: true });
- };
-
- /**
- * Main entrance
- */
- insertUiButtonsOnTop();
- insertUiControlElementsPerRow();
- insertModelToBody();
- setupObserverForTvPage();
- })();