PornoLab Batch Downloader

Batch download all torrents from search results on PornoLab

  1. // ==UserScript==
  2. // @name PornoLab Batch Downloader
  3. // @namespace copyMister
  4. // @version 1.0
  5. // @description Batch download all torrents from search results on PornoLab
  6. // @description:ru Массовое скачивание торрент-файлов из результатов поиска на PornoLab
  7. // @author copyMister
  8. // @license MIT
  9. // @match https://pornolab.net/forum/tracker.php*
  10. // @match https://pornolab.cc/forum/tracker.php*
  11. // @match https://pornolab.biz/forum/tracker.php*
  12. // @match https://pornolab.lib/forum/tracker.php*
  13. // @require https://cdn.jsdelivr.net/npm/fflate@0.8.0/umd/index.min.js
  14. // @require https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js
  15. // @require https://cdn.jsdelivr.net/npm/drag-check-js@2.0.2/dist/dragcheck.min.js
  16. // @icon https://www.google.com/s2/favicons?sz=64&domain=pornolab.net
  17. // @grant none
  18. // @homepageURL https://pornolab.net/forum/viewtopic.php?t=2714164
  19. // ==/UserScript==
  20.  
  21. /* global fflate, saveAs, DragCheck */
  22.  
  23. var waitTime = 500; // сколько мс ждать при скачивании очередного торрента (по умолчанию 0.5 сек)
  24. var batchBtn, downBtns, count, files;
  25. var fileArray = {};
  26.  
  27. function addTorrent(url, total) {
  28. var torName;
  29. var torId = url.split('=')[1];
  30. var xhr = new XMLHttpRequest();
  31.  
  32. xhr.open('POST', url, true);
  33. xhr.responseType = 'arraybuffer';
  34. xhr.onloadstart = function() {
  35. document.querySelector('#batch-down').textContent = 'Загрузка... ' + (total - count);
  36. count++;
  37. };
  38. xhr.onload = function() {
  39. torName = xhr.getResponseHeader('Content-Disposition').match(/name="([^"]+)"/);
  40. if (torName && xhr.response) {
  41. files++;
  42. torName = decodeURIComponent(torName[1]);
  43. fileArray[torName] = new Uint8Array(xhr.response);
  44. console.log('Success: #' + torId + '. ' + torName);
  45. }
  46. if (count == total) saveZip();
  47. };
  48. xhr.onerror = function() {
  49. console.log('Error: #' + torId + '. ' + xhr.status + ' ' + xhr.statusText);
  50. if (count == total) saveZip();
  51. };
  52. xhr.ontimeout = function() {
  53. console.log('Timeout: #' + torId);
  54. if (count == total) saveZip();
  55. };
  56. xhr.onabort = function() {
  57. console.log('Abort: #' + torId);
  58. if (count == total) saveZip();
  59. };
  60. xhr.send();
  61. }
  62.  
  63. function saveZip() {
  64. var fileName, page, archive;
  65. var add = '';
  66. var date = new Date().toISOString().substr(2, 8);
  67.  
  68. page = document.querySelector('.bottom_info > .nav > p > b');
  69. if (page) {
  70. add = ' #' + page.textContent;
  71. }
  72.  
  73. fileName = document.querySelector('#title-search').value || 'torrents';
  74. fileName = '[plab] [' + date + '] ' + fileName + add + ' [' + files + '].zip';
  75.  
  76. console.log('Generating archive...');
  77. archive = fflate.zipSync(fileArray, {level: 1});
  78. archive = new Blob([archive]);
  79. saveAs(archive, fileName);
  80. batchBtn.textContent = 'Готово!';
  81. batchBtn.disabled = false;
  82. }
  83.  
  84. function updateBatchBtnText() {
  85. var selCount = document.querySelectorAll('.sel-cbox:checked').length;
  86. downBtns = document.querySelectorAll('#tor-tbl .dl-stub');
  87.  
  88. if (selCount) {
  89. batchBtn.textContent = 'Скачать выдел. ' + selCount;
  90. } else {
  91. batchBtn.textContent = 'Скачать все ' + downBtns.length;
  92. }
  93. }
  94.  
  95. function toggleRowBackground(cbox, checked) {
  96. var td;
  97. if (checked) {
  98. for (td of cbox.closest('tr').children) {
  99. td.style.setProperty('background-color', 'rgba(255, 255, 255, .01)', 'important');
  100. }
  101. } else {
  102. for (td of cbox.closest('tr').children) {
  103. td.style.removeProperty('background-color');
  104. }
  105. }
  106. }
  107.  
  108. function processTorrentRows(trs) {
  109. var selHtml = '<td class="row4 sel-td" style="padding: 15px; position: relative;"><label class="sel-label" style="user-select: none; margin: 0; position: absolute; left: 0; top: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;"><input class="sel-cbox" type="checkbox" style="margin: 0;"></label></td>';
  110. var cboxArray = [];
  111. var tdHtml, hasDown;
  112.  
  113. trs.forEach(function(tr) {
  114. hasDown = tr.querySelector('.dl-stub');
  115. tdHtml = selHtml;
  116.  
  117. if (!hasDown) {
  118. tdHtml = '<td class="row4" style="padding: 15px; user-select: none;"></td>';
  119. }
  120. tr.querySelector('td:last-child').insertAdjacentHTML('afterend', tdHtml);
  121.  
  122. if (hasDown) {
  123. tr.querySelector('.sel-cbox').addEventListener('change', function() {
  124. toggleRowBackground(this, this.checked);
  125. updateBatchBtnText();
  126. });
  127. cboxArray.push(tr.querySelector('.sel-label'));
  128. }
  129. });
  130.  
  131. new DragCheck({
  132. checkboxes: cboxArray,
  133. getChecked: function(label) {
  134. return label.firstElementChild.checked;
  135. },
  136. setChecked: function(label, state) {
  137. label.firstElementChild.checked = state;
  138. },
  139. onChange: function(label) {
  140. var cbox = label.firstElementChild;
  141. toggleRowBackground(cbox, cbox.checked);
  142. updateBatchBtnText();
  143. }
  144. });
  145.  
  146. setTimeout(function() {
  147. updateBatchBtnText();
  148. }, 100);
  149. }
  150.  
  151. (function() {
  152. 'use strict';
  153.  
  154. var url, td, selBoxes;
  155. var batchBtnHtml = '<div class="border row2" style="text-align: center; margin-bottom: 6px; padding: 5px 0; border-width: 1px; position: sticky; top: 0; z-index: 1; display: flex; justify-content: center;"><button id="sel-all" title="Выделить все торренты" class="row5 clickable" style="width: 90px; height: 20px; margin-right: 10px; border: 1px solid #999999; font-family: Verdana,sans-serif; font-size: 10px;">☑️ Выдел. всё</button><button id="batch-down" class="row1 clickable" style="width: 140px; height: 20px; border: 1px solid gray; border-radius: 3px; font-family: Verdana,sans-serif; font-size: 11px; font-weight: bold;"></button><button id="unsel-all" title="Снять всё выделение" class="row5 clickable" style="width: 90px; height: 20px; margin-left: 10px; border: 1px solid #999999; font-family: Verdana,sans-serif; font-size: 10px;">✖️ Снять всё</button></div>';
  156.  
  157. if (document.querySelector('.dl-stub')) {
  158. document.querySelector('#search-results').insertAdjacentHTML('afterbegin', batchBtnHtml);
  159. batchBtn = document.querySelector('#batch-down');
  160.  
  161. batchBtn.addEventListener('click', function() {
  162. batchBtn.disabled = true;
  163. count = 0;
  164. files = 0;
  165. fileArray = {};
  166.  
  167. selBoxes = document.querySelectorAll('.sel-cbox:checked');
  168.  
  169. if (downBtns.length) {
  170. if (selBoxes.length) {
  171. selBoxes.forEach(function(cbox, ind) {
  172. var btn = cbox.closest('tr').querySelector('.dl-stub');
  173. setTimeout(function() {
  174. addTorrent(btn.href, selBoxes.length);
  175. }, waitTime + (ind * waitTime));
  176. });
  177. } else {
  178. downBtns.forEach(function(btn, ind) {
  179. setTimeout(function() {
  180. addTorrent(btn.href, downBtns.length);
  181. }, waitTime + (ind * waitTime));
  182. });
  183. }
  184. }
  185. });
  186.  
  187. document.querySelector('#tor-tbl th:last-child').insertAdjacentHTML('afterend', '<th data-sorter="false"></th>');
  188. document.querySelector('#tor-tbl > tfoot td').colSpan += 1;
  189.  
  190. processTorrentRows(document.querySelectorAll('#tor-tbl > tbody > tr'));
  191.  
  192. document.querySelector('#unsel-all').addEventListener('click', function() {
  193. document.querySelectorAll('.sel-cbox:checked').forEach(function(cbox) {
  194. cbox.checked = false;
  195. toggleRowBackground(cbox, false);
  196. });
  197. updateBatchBtnText();
  198. });
  199.  
  200. document.querySelector('#sel-all').addEventListener('click', function() {
  201. document.querySelectorAll('.sel-cbox').forEach(function(cbox) {
  202. cbox.checked = true;
  203. toggleRowBackground(cbox, true);
  204. });
  205. updateBatchBtnText();
  206. });
  207.  
  208. document.addEventListener('new-torrents', function(e) {
  209. processTorrentRows(e.detail.querySelectorAll('tr'));
  210. });
  211. }
  212. })();