Skip to content

Instantly share code, notes, and snippets.

@xl5866476
Last active August 5, 2023 19:38
Show Gist options
  • Save xl5866476/b8b04a44cff20e27a48e4fcac726f36e to your computer and use it in GitHub Desktop.
Save xl5866476/b8b04a44cff20e27a48e4fcac726f36e to your computer and use it in GitHub Desktop.
user script for rarbg, batch copy magnet links
// ==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();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment