Greasy Fork is available in English.

rarbg-magnet-batch-copy

Display checkboxes and magnet icons on rarbg search result page, you can batch copy magnet links.

  1. // ==UserScript==
  2. // @name rarbg-magnet-batch-copy
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description Display checkboxes and magnet icons on rarbg search result page, you can batch copy magnet links.
  6. // @author Xavier Lee
  7. // @match *://rarbg.to/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=rarbg.to
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. "use strict";
  14. /**
  15. * Global constant
  16. */
  17. const itemCheckboxClass = "rarbg-magnet-batch-item-checkbox";
  18. const modalWrapperId = "rarbg-magnet-batch-modal-wrapper";
  19. const modalContentId = "rarbg-magnet-batch-modal-content";
  20. const selectAllText = "✅Select All";
  21. const unselectAllText = "🔳Unselect All";
  22. const copySelectedButtonText = "🧲📋Copy Selected Magnet Links";
  23. // selectors on the rarbg details page
  24. const rarbgPageMagnetIconSelector = "a[href^='magnet:?']";
  25. const rarbgPageTorrentLinkSelector = "a[href$='.torrent']";
  26. // selectors on the rarbg search result page
  27. const itemRowSecondTdSelector = "tr.lista2 > td:nth-child(2)";
  28. const titleLinkInSecondTdSelector = "a[title]";
  29. const headerRowSecondCellSelector =
  30. "table.lista2t > tbody > tr:nth-child(1) > td:nth-child(2)";
  31. const itemRowSelector = "table.lista2t > tbody > tr.lista2";
  32.  
  33. /**
  34. * Create control elements
  35. */
  36. const createControlElement = function (controlType) {
  37. const controlDetailsObject = {
  38. selectAllButton: {
  39. element: "button",
  40. innerText: selectAllText,
  41. attributes: {
  42. style: "margin-right:10px;",
  43. },
  44. },
  45. copySelectedMagnetButton: {
  46. element: "button",
  47. innerText: copySelectedButtonText,
  48. attributes: {
  49. style: "margin-left:10px;",
  50. },
  51. },
  52. lineBreak: {
  53. element: "br",
  54. },
  55. itemCheckbox: {
  56. element: "input",
  57. attributes: {
  58. type: "checkbox",
  59. style: "height: 12px;",
  60. class: itemCheckboxClass,
  61. },
  62. },
  63. magnetIcon: {
  64. element: "img",
  65. attributes: {
  66. title: "copy magnet link",
  67. class: "copyManet",
  68. src: "https://dyncdn.me/static/20/img/magnet.gif",
  69. style: "margin: 0px 5px;",
  70. },
  71. },
  72. torrentIcon: {
  73. element: "img",
  74. attributes: {
  75. title: "download torrent file",
  76. class: "downloadTorrent",
  77. src: "https://dyncdn.me/static/20/img/16x16/download.png",
  78. },
  79. },
  80. modalWrapper: {
  81. element: "div",
  82. innerText: "",
  83. attributes: {
  84. id: modalWrapperId,
  85. style:
  86. "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);",
  87. },
  88. },
  89. modalContent: {
  90. element: "div",
  91. innerText: "",
  92. attributes: {
  93. id: modalContentId,
  94. style:
  95. "background-color:#fefefe; margin:auto; padding:20px; border:1px solid #888; width:80%; color:#000;",
  96. },
  97. },
  98. };
  99. const possibleTypes = Object.keys(controlDetailsObject);
  100. if (!possibleTypes.includes(controlType)) {
  101. throw `createControlElement function, argument controlType is ${controlType}, expecting ${possibleTypes}`;
  102. }
  103. const controlDetail = controlDetailsObject[controlType];
  104. const controlAttributes = controlDetail.attributes;
  105. const controlElement = document.createElement(controlDetail.element);
  106. controlElement.innerText = controlDetail.innerText;
  107. for (const attributeName in controlAttributes) {
  108. if (Object.hasOwnProperty.call(controlAttributes, attributeName)) {
  109. const attributeValue = controlAttributes[attributeName];
  110. controlElement.setAttribute(attributeName, attributeValue);
  111. }
  112. }
  113. return controlElement;
  114. };
  115.  
  116. /**
  117. * This function UPDATE the array of rarbgMagnetTorrentItems
  118. * rarbgMagnetTorrentItems should be something like:
  119. [
  120. {
  121. name: "This.Is.Us.S04E18.1080p.AMZN.WEBRip.DDP5.1.x264-KiNGS[rartv]",
  122. pageUrl: "https://rarbg.to/torrent/dti6lc1",
  123. magnetUrl: null,
  124. torrentUrl: null,
  125. },
  126. {
  127. name: "This.Is.Us.S04E17.1080p.AMZN.WEBRip.DDP5.1.x264-KiNGS[rartv]",
  128. pageUrl: "https://rarbg.to/torrent/pnljgd2",
  129. magnetUrl: null,
  130. torrentUrl: null,
  131. },
  132. ]
  133. * Then the function should update the magnetUrl and torrentUrl in each object of the array
  134. */
  135. const updateRarbgMagnetTorrentItems = async function (items) {
  136. const responses = await Promise.all(
  137. items.map((item) => fetch(item.pageUrl))
  138. );
  139. const htmls = await Promise.all(responses.map((r) => r.text()));
  140. htmls.forEach((html) => {
  141. const parser = new DOMParser();
  142. const doc = parser.parseFromString(html, "text/html");
  143. const torrentLink = doc.querySelector(rarbgPageTorrentLinkSelector);
  144. const magnetIcon = doc.querySelector(rarbgPageMagnetIconSelector);
  145. const pageName = torrentLink.innerText;
  146. const magnetUrl = magnetIcon.getAttribute("href");
  147. const torrentUrl = torrentLink.getAttribute("href");
  148. const currentName = items.find((item) => item.name === pageName);
  149. currentName.magnetUrl = magnetUrl;
  150. currentName.torrentUrl = torrentUrl;
  151. });
  152. return items;
  153. };
  154.  
  155. /**
  156. * This function GENERATE one rarbgMagnetTorrentItem obj from the second td in an item row
  157. */
  158. const generateRarbgMagnetTorrentItem = function (td) {
  159. const titleLink = td.querySelector(titleLinkInSecondTdSelector);
  160. return {
  161. name: titleLink.innerText,
  162. pageUrl: titleLink.getAttribute("href"),
  163. magnetUrl: null,
  164. torrentUrl: null,
  165. };
  166. };
  167.  
  168. /**
  169. * This function GENERATE the array of rarbgMagnetTorrentItems for selected
  170. * Or for a single item in an array when click the icon directly
  171. */
  172. const generateRarbgMagnetTorrentItemsForSelected = function (
  173. targetTd = null
  174. ) {
  175. if (targetTd) {
  176. return [generateRarbgMagnetTorrentItem(targetTd)];
  177. }
  178. const itemRowSecondTds = document.querySelectorAll(itemRowSecondTdSelector);
  179. return Array.from(itemRowSecondTds)
  180. .filter((td) => td.querySelector(`.${itemCheckboxClass}`).checked)
  181. .map((td) => generateRarbgMagnetTorrentItem(td));
  182. };
  183.  
  184. /**
  185. * Select or Un-select all item checkboxes
  186. */
  187. const selectOrUnselectAllItems = function (event) {
  188. const targetButton = event.target;
  189. const currentButtonText = targetButton.innerText;
  190. const checkboxes = document.querySelectorAll(`.${itemCheckboxClass}`);
  191. if (currentButtonText === selectAllText) {
  192. checkboxes.forEach((checkbox) => (checkbox.checked = true));
  193. targetButton.innerText = unselectAllText;
  194. } else {
  195. checkboxes.forEach((checkbox) => (checkbox.checked = false));
  196. targetButton.innerText = selectAllText;
  197. }
  198. };
  199.  
  200. /**
  201. * This function showing a message to user
  202. */
  203.  
  204. const showMessage = function (str) {
  205. const modalWrapper = document.getElementById(modalWrapperId);
  206. const modalContent = document.getElementById(modalContentId);
  207. modalContent.innerText = str;
  208. modalWrapper.style.display = "block";
  209.  
  210. setTimeout(() => {
  211. modalWrapper.style.display = "none";
  212. modalContent.innerText = "";
  213. }, 2000);
  214. };
  215.  
  216. /**
  217. * This function get UPDATED rarbgMagnetTorrentItems for selected or targeted
  218. */
  219. const getUpdatedRarbgMagnetTorrentItems = async function (event) {
  220. let targetTdOrNull;
  221. if (event.target.tagName !== "BUTTON") {
  222. // means user clicked icon
  223. targetTdOrNull = event.target.parentNode;
  224. }
  225. const rarbgMagnetTorrentItems =
  226. generateRarbgMagnetTorrentItemsForSelected(targetTdOrNull);
  227. await updateRarbgMagnetTorrentItems(rarbgMagnetTorrentItems);
  228. return rarbgMagnetTorrentItems;
  229. };
  230.  
  231. /**
  232. * This function copy selected items' magnet urls to clipboard
  233. */
  234. const copySelectedMagnetUrls = async function (event) {
  235. const rarbgMagnetTorrentItems = await getUpdatedRarbgMagnetTorrentItems(
  236. event
  237. );
  238. const textToCopy = rarbgMagnetTorrentItems
  239. .map((item) => item.magnetUrl)
  240. .join("\t\n");
  241. await navigator.clipboard.writeText(textToCopy);
  242. showMessage(
  243. `successfully copied ${rarbgMagnetTorrentItems.length} links to clipboard:
  244. ${textToCopy}`
  245. );
  246. };
  247.  
  248. /**
  249. * This function download the torrent file
  250. */
  251. const downloadSelectedTorrent = async function (event) {
  252. const rarbgMagnetTorrentItems = await getUpdatedRarbgMagnetTorrentItems(
  253. event
  254. );
  255. const torrentUrl = rarbgMagnetTorrentItems[0].torrentUrl;
  256. const name = rarbgMagnetTorrentItems[0].name;
  257. const link = document.createElement("a");
  258. link.href = torrentUrl;
  259. link.setAttribute("target", "_blank");
  260. link.click();
  261. };
  262.  
  263. /**
  264. * Insert UI buttons on top of the table
  265. */
  266. const insertUiButtonsOnTop = function (target = null) {
  267. const scopeDoc = target ? target : document;
  268. const headerRowSecondCell = scopeDoc.querySelector(
  269. headerRowSecondCellSelector
  270. );
  271. if (!headerRowSecondCell) {
  272. return;
  273. }
  274. const selectAllButton = createControlElement("selectAllButton");
  275. selectAllButton.addEventListener("click", selectOrUnselectAllItems);
  276. const copySelectedMagnetButton = createControlElement(
  277. "copySelectedMagnetButton"
  278. );
  279. copySelectedMagnetButton.addEventListener("click", copySelectedMagnetUrls);
  280. headerRowSecondCell.append(selectAllButton, copySelectedMagnetButton);
  281. };
  282.  
  283. /**
  284. * Insert UI controls into each item row
  285. */
  286. const insertUiControlElementsPerRow = function (target = null) {
  287. const scopeDoc = target ? target : document;
  288. const itemRows = scopeDoc.querySelectorAll(itemRowSelector);
  289.  
  290. itemRows.forEach((row, i) => {
  291. const itemLinkTd = row.querySelectorAll("td")[1];
  292. const itemCheckbox = createControlElement("itemCheckbox");
  293. const magnetIcon = createControlElement("magnetIcon");
  294. magnetIcon.addEventListener("click", copySelectedMagnetUrls);
  295. const torrentIcon = createControlElement("torrentIcon");
  296. torrentIcon.addEventListener("click", downloadSelectedTorrent);
  297. const lineBreak = createControlElement("lineBreak");
  298. itemLinkTd.prepend(itemCheckbox, magnetIcon, torrentIcon, lineBreak);
  299. });
  300. };
  301.  
  302. /**
  303. * Insert model related element for notification
  304. */
  305. const insertModelToBody = function () {
  306. const modalWrapper = createControlElement("modalWrapper");
  307. const modalContent = createControlElement("modalContent");
  308. modalWrapper.append(modalContent);
  309. document.body.append(modalWrapper);
  310. };
  311.  
  312. /**
  313. * Setting up the observer when click expanding on tv page
  314. */
  315. const setupObserverForTvPage = function () {
  316. const contentTable = document.querySelector(".lista-rounded");
  317. const observer = new MutationObserver(function (mutations) {
  318. mutations.forEach((mutation) => {
  319. if (mutation.removedNodes.length > 0) {
  320. insertUiButtonsOnTop(mutation.target);
  321. insertUiControlElementsPerRow(mutation.target);
  322. }
  323. });
  324. });
  325. observer.observe(contentTable, { childList: true, subtree: true });
  326. };
  327.  
  328. /**
  329. * Main entrance
  330. */
  331. insertUiButtonsOnTop();
  332. insertUiControlElementsPerRow();
  333. insertModelToBody();
  334. setupObserverForTvPage();
  335. })();