Last active 4 months ago

Miscelleanous tweaks applied to Nyaa.si

Alex's Avatar Alex revised this gist 4 months ago. Go to revision

No changes

Alex's Avatar Alex Thomassen revised this gist 4 months ago. Go to revision

1 file changed, 64 insertions, 6 deletions

nyaa-shit.user.js

@@ -3,7 +3,7 @@
3 3 // @namespace github.com/Decicus
4 4 // @match https://nyaa.si/*
5 5 // @grant GM_setClipboard
6 - // @version 1.4.1
6 + // @version 1.5.0
7 7 // @author Decicus
8 8 // @description Adds some extra functionality to Nyaa.
9 9 // ==/UserScript==
@@ -194,16 +194,21 @@ const titleReplacements = [
194 194 search: / \"[A-z0-9-!\s]+\"$/,
195 195 replacement: '',
196 196 },
197 - {
198 - comment: 'Move release group to the end',
199 - search: /^\[([-\w ]+)\] (.+)$/,
200 - replacement: '$2 [$1]',
201 - },
197 + // {
198 + // comment: 'Move release group to the end',
199 + // search: /^\[([-\w ]+)\] (.+)$/,
200 + // replacement: '$2 [$1]',
201 + // },
202 202 {
203 203 comment: 'Colon space to space dash space',
204 204 search: /: /g,
205 205 replacement: ' - ',
206 206 },
207 + {
208 + comment: 'Ensure seasons have two numbers',
209 + search: / \(Season (\d)\)/g,
210 + replacement: ' (Season 0$1)',
211 + },
207 212 {
208 213 comment: 'Season naming so Sonarr doesnt get confused because its dumb',
209 214 search: / \(Season (\d+)\)/g,
@@ -294,6 +299,58 @@ function titleHandler()
294 299 titleParent.insertAdjacentElement('afterbegin', buttonParent);
295 300 }
296 301
302 + /**
303 + * Used by `copyFilenameHandler()` to do the actual copying
304 + */
305 + function copyFilenameTrigger(ev)
306 + {
307 + const { target } = ev;
308 +
309 + // Trust no one, not even yourself
310 + if (!target) {
311 + return;
312 + }
313 +
314 + // We clone it here because I want to remove the file size that's normally at the end
315 + // If I simply do a `remove()` on the usual element, it will actually remove it from the DOM.
316 + const parent = target.parentElement.cloneNode(true);
317 + const fileSize = parent.querySelector('.file-size');
318 +
319 + if (fileSize) {
320 + fileSize.remove();
321 + }
322 +
323 + const filename = parent.textContent.trim();
324 +
325 + target.classList.remove('fa-file');
326 + target.classList.add('fa-check');
327 +
328 + GM_setClipboard(filename);
329 +
330 + setTimeout(() => {
331 + target.classList.remove('fa-check');
332 + target.classList.add('fa-file');
333 + }, 1500);
334 + }
335 +
336 + /**
337 + * Clicking the file icon in front of each file will allow you to copy the filename
338 + */
339 + async function copyFilenameHandler()
340 + {
341 + const fileList = document.querySelector('.torrent-file-list');
342 + if (!fileList) {
343 + return;
344 + }
345 +
346 + const fileIcons = fileList.querySelectorAll('.fa-file');
347 +
348 + for (const icon of fileIcons)
349 + {
350 + icon.addEventListener('click', copyFilenameTrigger);
351 + }
352 + }
353 +
297 354 function bbcodeConverter(html) {
298 355 html = html.replace(/<pre(.*?)>(.*?)<\/pre>/gmi, "[code]$2[/code]");
299 356 html = html.replace(/<h[1-7](.*?)>(.*?)<\/h[1-7]>/, "\n[h]$2[/h]\n");
@@ -559,6 +616,7 @@ function init() {
559 616 changeToCopy();
560 617 titleHandler();
561 618 descriptionHandler();
619 + copyFilenameHandler();
562 620 torrentListViewHandler();
563 621 calculateTotalSize();
564 622 widerContainers();

Alex's Avatar Alex Thomassen revised this gist 1 year ago. Go to revision

1 file changed, 25 insertions, 15 deletions

nyaa-shit.user.js

@@ -3,7 +3,7 @@
3 3 // @namespace github.com/Decicus
4 4 // @match https://nyaa.si/*
5 5 // @grant GM_setClipboard
6 - // @version 1.4.0
6 + // @version 1.4.1
7 7 // @author Decicus
8 8 // @description Adds some extra functionality to Nyaa.
9 9 // ==/UserScript==
@@ -83,6 +83,9 @@ function calculateTotalSize()
83 83 return;
84 84 }
85 85
86 + const sampleTorrent = "https://nyaa.si/download/222409.torrent";
87 + const sampleMagnet = "magnet:?xt=urn:btih:db0ffe8174317b0b0ee4beb7b54f558bb9089746&dn=%5Beoy%5D%20dark%20dragoon%20-%2001.txt&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce";
88 +
86 89 const html = `<tr class="default" id="all-torrents-size">
87 90 <td class="text-center">
88 91 <a href="#all-torrents-size" title="Total size">
@@ -92,7 +95,12 @@ function calculateTotalSize()
92 95 <td colspan="2">
93 96 <a href="#all-torrents-size">Total size</a>
94 97 </td>
95 - <td class="text-center"><!-- Download --></td>
98 + <td class="text-center">
99 + <!-- These elements purely exist to avoid any issues with extensions or userscripts that would normally expect valid torrent/magnet URIs -->
100 + <!-- In my case I only found an issue with NyaaBlue from SeaDex -->
101 + <a href="${sampleTorrent}" style="display: none;"><!-- Download torrent --></a>
102 + <a href="${sampleMagnet}" style="display: none;"><!-- Magnet URI --></a>
103 + </td>
96 104 <td class="text-center">${totalSize}</td>
97 105 <td class="text-center"><!-- Timestamp --></td>
98 106 <td class="text-center"><!-- Seeders --></td>
@@ -528,21 +536,23 @@ function torrentListViewHandler()
528 536 parentElement.insertAdjacentElement('beforebegin', amountCopyLinks);
529 537 }
530 538
531 - function changeContainers()
539 + /**
540 + * This previously changed some `.container` classes to `.container-fluid`,
541 + * but since that breaks other userscripts, such as NyaaBlue from SeaDex,
542 + * I figured it'd be better to simply override the CSS width that .container usually applies.
543 + */
544 + function widerContainers()
532 545 {
533 - const selectors = ['.navbar-static-top > .container', 'body > .container'];
534 - for (const selector of selectors)
535 - {
536 - const element = document.querySelector(selector);
537 - if (!element) {
538 - console.error('Could not find any element with selector', selector);
539 - continue;
546 + const styleDoc = document.createElement('style');
547 + styleDoc.setAttribute('type', 'text/css');
548 +
549 + styleDoc.textContent = `
550 + .container {
551 + width: auto !important;
540 552 }
553 + `;
541 554
542 - element.classList.remove('container');
543 - element.classList.add('container-fluid');
544 - console.log('Element updated with container-fluid', selector, element);
545 - }
555 + document.head.insertAdjacentElement('beforeend', styleDoc);
546 556 }
547 557
548 558 function init() {
@@ -551,7 +561,7 @@ function init() {
551 561 descriptionHandler();
552 562 torrentListViewHandler();
553 563 calculateTotalSize();
554 - changeContainers();
564 + widerContainers();
555 565 }
556 566
557 567 init();

Alex's Avatar Alex Thomassen revised this gist 2 years ago. Go to revision

1 file changed, 19 insertions, 2 deletions

nyaa-shit.user.js

@@ -3,7 +3,7 @@
3 3 // @namespace github.com/Decicus
4 4 // @match https://nyaa.si/*
5 5 // @grant GM_setClipboard
6 - // @version 1.3.1
6 + // @version 1.4.0
7 7 // @author Decicus
8 8 // @description Adds some extra functionality to Nyaa.
9 9 // ==/UserScript==
@@ -60,7 +60,6 @@ function calculateTotalSize()
60 60 const torrentBytes = sizeNumber * multiplier;
61 61
62 62 totalBytes += torrentBytes;
63 - console.log(totalBytes);
64 63 }
65 64
66 65 const multiplierKeys = Object.keys(multipliers).reverse();
@@ -529,12 +528,30 @@ function torrentListViewHandler()
529 528 parentElement.insertAdjacentElement('beforebegin', amountCopyLinks);
530 529 }
531 530
531 + function changeContainers()
532 + {
533 + const selectors = ['.navbar-static-top > .container', 'body > .container'];
534 + for (const selector of selectors)
535 + {
536 + const element = document.querySelector(selector);
537 + if (!element) {
538 + console.error('Could not find any element with selector', selector);
539 + continue;
540 + }
541 +
542 + element.classList.remove('container');
543 + element.classList.add('container-fluid');
544 + console.log('Element updated with container-fluid', selector, element);
545 + }
546 + }
547 +
532 548 function init() {
533 549 changeToCopy();
534 550 titleHandler();
535 551 descriptionHandler();
536 552 torrentListViewHandler();
537 553 calculateTotalSize();
554 + changeContainers();
538 555 }
539 556
540 557 init();

Alex's Avatar Alex Thomassen revised this gist 2 years ago. Go to revision

1 file changed, 5 insertions, 1 deletion

nyaa-shit.user.js

@@ -3,7 +3,7 @@
3 3 // @namespace github.com/Decicus
4 4 // @match https://nyaa.si/*
5 5 // @grant GM_setClipboard
6 - // @version 1.3.0
6 + // @version 1.3.1
7 7 // @author Decicus
8 8 // @description Adds some extra functionality to Nyaa.
9 9 // ==/UserScript==
@@ -80,6 +80,10 @@ function calculateTotalSize()
80 80 }
81 81
82 82 const torrentList = document.querySelector('.torrent-list tbody');
83 + if (!torrentList) {
84 + return;
85 + }
86 +
83 87 const html = `<tr class="default" id="all-torrents-size">
84 88 <td class="text-center">
85 89 <a href="#all-torrents-size" title="Total size">

Alex's Avatar Alex Thomassen revised this gist 2 years ago. Go to revision

1 file changed, 68 insertions, 68 deletions

nyaa-shit.user.js

@@ -81,21 +81,21 @@ function calculateTotalSize()
81 81
82 82 const torrentList = document.querySelector('.torrent-list tbody');
83 83 const html = `<tr class="default" id="all-torrents-size">
84 - <td class="text-center">
85 - <a href="#all-torrents-size" title="Total size">
86 - 💽
87 - </a>
88 - </td>
89 - <td colspan="2">
84 + <td class="text-center">
85 + <a href="#all-torrents-size" title="Total size">
86 + 💽
87 + </a>
88 + </td>
89 + <td colspan="2">
90 90 <a href="#all-torrents-size">Total size</a>
91 - </td>
92 - <td class="text-center"><!-- Download --></td>
93 - <td class="text-center">${totalSize}</td>
94 - <td class="text-center"><!-- Timestamp --></td>
95 - <td class="text-center"><!-- Seeders --></td>
96 - <td class="text-center"><!-- Leechers --></td>
97 - <td class="text-center"><!-- Completed --></td>
98 - </tr>`;
91 + </td>
92 + <td class="text-center"><!-- Download --></td>
93 + <td class="text-center">${totalSize}</td>
94 + <td class="text-center"><!-- Timestamp --></td>
95 + <td class="text-center"><!-- Seeders --></td>
96 + <td class="text-center"><!-- Leechers --></td>
97 + <td class="text-center"><!-- Completed --></td>
98 + </tr>`;
99 99
100 100 torrentList.insertAdjacentHTML('beforeend', html);
101 101 }
@@ -285,63 +285,63 @@ function titleHandler()
285 285
286 286 function bbcodeConverter(html) {
287 287 html = html.replace(/<pre(.*?)>(.*?)<\/pre>/gmi, "[code]$2[/code]");
288 - html = html.replace(/<h[1-7](.*?)>(.*?)<\/h[1-7]>/, "\n[h]$2[/h]\n");
289 - //paragraph handling:
290 - //- if a paragraph opens on the same line as another one closes, insert an extra blank line
291 - //- opening tag becomes two line breaks
292 - //- closing tags are just removed
293 - // html += html.replace(/<\/p><p/<\/p>\n<p/gi;
294 - // html += html.replace(/<p[^>]*>/\n\n/gi;
295 - // html += html.replace(/<\/p>//gi;
288 + html = html.replace(/<h[1-7](.*?)>(.*?)<\/h[1-7]>/, "\n[h]$2[/h]\n");
289 + //paragraph handling:
290 + //- if a paragraph opens on the same line as another one closes, insert an extra blank line
291 + //- opening tag becomes two line breaks
292 + //- closing tags are just removed
293 + // html += html.replace(/<\/p><p/<\/p>\n<p/gi;
294 + // html += html.replace(/<p[^>]*>/\n\n/gi;
295 + // html += html.replace(/<\/p>//gi;
296 296
297 297 html = html.replace(/<\/p>\n<p>/gi, "\n\n\n");
298 298
299 - html = html.replace(/<br(.*?)>/gi, "\n");
300 - html = html.replace(/<textarea(.*?)>(.*?)<\/textarea>/gmi, "\[code]$2\[\/code]");
301 - html = html.replace(/<b>/gi, "[b]");
302 - html = html.replace(/<i>/gi, "[i]");
303 - html = html.replace(/<u>/gi, "[u]");
304 - html = html.replace(/<\/b>/gi, "[/b]");
305 - html = html.replace(/<\/i>/gi, "[/i]");
306 - html = html.replace(/<\/u>/gi, "[/u]");
307 - html = html.replace(/<em>/gi, "[b]");
308 - html = html.replace(/<\/em>/gi, "[/b]");
309 - html = html.replace(/<strong>/gi, "[b]");
310 - html = html.replace(/<\/strong>/gi, "[/b]");
311 - html = html.replace(/<cite>/gi, "[i]");
312 - html = html.replace(/<\/cite>/gi, "[/i]");
313 - html = html.replace(/<font color="(.*?)">(.*?)<\/font>/gmi, "[color=$1]$2[/color]");
314 - html = html.replace(/<font color=(.*?)>(.*?)<\/font>/gmi, "[color=$1]$2[/color]");
315 - html = html.replace(/<link(.*?)>/gi, "");
316 - html = html.replace(/<li(.*?)>(.*?)<\/li>/gi, "[*]$2");
317 - html = html.replace(/<ul(.*?)>/gi, "[list]");
318 - html = html.replace(/<\/ul>/gi, "[/list]");
319 - html = html.replace(/<div>/gi, "\n");
320 - html = html.replace(/<\/div>/gi, "\n");
321 - html = html.replace(/<td(.*?)>/gi, " ");
322 - html = html.replace(/<tr(.*?)>/gi, "\n");
323 -
324 - html = html.replace(/<img(.*?)src="(.*?)"(.*?)>/gi, "[img]$2[/img]");
325 - html = html.replace(/<a(.*?)href="(.*?)"(.*?)>(.*?)<\/a>/gi, "[url=$2]$4[/url]");
326 -
327 - html = html.replace(/<head>(.*?)<\/head>/gmi, "");
328 - html = html.replace(/<object>(.*?)<\/object>/gmi, "");
329 - html = html.replace(/<script(.*?)>(.*?)<\/script>/gmi, "");
330 - html = html.replace(/<style(.*?)>(.*?)<\/style>/gmi, "");
331 - html = html.replace(/<title>(.*?)<\/title>/gmi, "");
332 - html = html.replace(/<!--(.*?)-->/gmi, "\n");
333 -
334 - html = html.replace(/\/\//gi, "/");
335 - html = html.replace(/http:\//gi, "http://");
336 -
337 - html = html.replace(/<(?:[^>'"]*|(['"]).*?\1)*>/gmi, "");
338 - html = html.replace(/\r\r/gi, "");
339 - html = html.replace(/\[img]\//gi, "[img]");
340 - html = html.replace(/\[url=\//gi, "[url=");
341 -
342 - html = html.replace(/(\S)\n/gi, "$1 ");
343 -
344 - return html;
299 + html = html.replace(/<br(.*?)>/gi, "\n");
300 + html = html.replace(/<textarea(.*?)>(.*?)<\/textarea>/gmi, "\[code]$2\[\/code]");
301 + html = html.replace(/<b>/gi, "[b]");
302 + html = html.replace(/<i>/gi, "[i]");
303 + html = html.replace(/<u>/gi, "[u]");
304 + html = html.replace(/<\/b>/gi, "[/b]");
305 + html = html.replace(/<\/i>/gi, "[/i]");
306 + html = html.replace(/<\/u>/gi, "[/u]");
307 + html = html.replace(/<em>/gi, "[b]");
308 + html = html.replace(/<\/em>/gi, "[/b]");
309 + html = html.replace(/<strong>/gi, "[b]");
310 + html = html.replace(/<\/strong>/gi, "[/b]");
311 + html = html.replace(/<cite>/gi, "[i]");
312 + html = html.replace(/<\/cite>/gi, "[/i]");
313 + html = html.replace(/<font color="(.*?)">(.*?)<\/font>/gmi, "[color=$1]$2[/color]");
314 + html = html.replace(/<font color=(.*?)>(.*?)<\/font>/gmi, "[color=$1]$2[/color]");
315 + html = html.replace(/<link(.*?)>/gi, "");
316 + html = html.replace(/<li(.*?)>(.*?)<\/li>/gi, "[*]$2");
317 + html = html.replace(/<ul(.*?)>/gi, "[list]");
318 + html = html.replace(/<\/ul>/gi, "[/list]");
319 + html = html.replace(/<div>/gi, "\n");
320 + html = html.replace(/<\/div>/gi, "\n");
321 + html = html.replace(/<td(.*?)>/gi, " ");
322 + html = html.replace(/<tr(.*?)>/gi, "\n");
323 +
324 + html = html.replace(/<img(.*?)src="(.*?)"(.*?)>/gi, "[img]$2[/img]");
325 + html = html.replace(/<a(.*?)href="(.*?)"(.*?)>(.*?)<\/a>/gi, "[url=$2]$4[/url]");
326 +
327 + html = html.replace(/<head>(.*?)<\/head>/gmi, "");
328 + html = html.replace(/<object>(.*?)<\/object>/gmi, "");
329 + html = html.replace(/<script(.*?)>(.*?)<\/script>/gmi, "");
330 + html = html.replace(/<style(.*?)>(.*?)<\/style>/gmi, "");
331 + html = html.replace(/<title>(.*?)<\/title>/gmi, "");
332 + html = html.replace(/<!--(.*?)-->/gmi, "\n");
333 +
334 + html = html.replace(/\/\//gi, "/");
335 + html = html.replace(/http:\//gi, "http://");
336 +
337 + html = html.replace(/<(?:[^>'"]*|(['"]).*?\1)*>/gmi, "");
338 + html = html.replace(/\r\r/gi, "");
339 + html = html.replace(/\[img]\//gi, "[img]");
340 + html = html.replace(/\[url=\//gi, "[url=");
341 +
342 + html = html.replace(/(\S)\n/gi, "$1 ");
343 +
344 + return html;
345 345 }
346 346
347 347 function descriptionHandler()

Alex's Avatar Alex Thomassen revised this gist 2 years ago. Go to revision

1 file changed, 74 insertions, 1 deletion

nyaa-shit.user.js

@@ -3,12 +3,21 @@
3 3 // @namespace github.com/Decicus
4 4 // @match https://nyaa.si/*
5 5 // @grant GM_setClipboard
6 - // @version 1.2.0
6 + // @version 1.3.0
7 7 // @author Decicus
8 8 // @description Adds some extra functionality to Nyaa.
9 9 // ==/UserScript==
10 10
11 11 let copyAmount = 5;
12 + const multipliers = {
13 + Bytes: 1,
14 + KiB: 1024,
15 + MiB: 1024 * 1024,
16 + GiB: 1024 * 1024 * 1024,
17 + TiB: 1024 * 1024 * 1024 * 1024,
18 + };
19 +
20 + const sizesRegex = /(Bytes|KiB|MiB|GiB|TiB)/g;
12 21
13 22 function getElements(linkType = 'download')
14 23 {
@@ -28,6 +37,69 @@ function getMagnets()
28 37 return getElements('magnet');
29 38 }
30 39
40 + /**
41 + * Calculate the approximate total size of all torrents listed in the table and append it as an information row at the bottom of the table
42 + */
43 + function calculateTotalSize()
44 + {
45 + const timestamps = document.querySelectorAll('.torrent-list td[data-timestamp]');
46 + let totalBytes = 0;
47 + for (const timestamp of timestamps)
48 + {
49 + const sizeCell = timestamp.previousElementSibling;
50 + const size = sizeCell.textContent.trim();
51 + let suffix = size.match(sizesRegex);
52 + if (!suffix || suffix.length < 1) {
53 + console.error('Could not find size in element', sizeCell);
54 + continue;
55 + }
56 +
57 + suffix = suffix[0];
58 + const multiplier = multipliers[suffix];
59 + const sizeNumber = parseFloat(size.replace(` ${suffix}`, '').trim());
60 + const torrentBytes = sizeNumber * multiplier;
61 +
62 + totalBytes += torrentBytes;
63 + console.log(totalBytes);
64 + }
65 +
66 + const multiplierKeys = Object.keys(multipliers).reverse();
67 + let totalSize = `${totalBytes} Bytes`;
68 + for (const key of multiplierKeys)
69 + {
70 + if (totalBytes === 0) {
71 + break;
72 + }
73 +
74 + const divideBy = multipliers[key];
75 + const result = totalBytes / divideBy;
76 + if (result > 1) {
77 + totalSize = `${result.toFixed(2)} ${key}`;
78 + break;
79 + }
80 + }
81 +
82 + const torrentList = document.querySelector('.torrent-list tbody');
83 + const html = `<tr class="default" id="all-torrents-size">
84 + <td class="text-center">
85 + <a href="#all-torrents-size" title="Total size">
86 + 💽
87 + </a>
88 + </td>
89 + <td colspan="2">
90 + <a href="#all-torrents-size">Total size</a>
91 + </td>
92 + <td class="text-center"><!-- Download --></td>
93 + <td class="text-center">${totalSize}</td>
94 + <td class="text-center"><!-- Timestamp --></td>
95 + <td class="text-center"><!-- Seeders --></td>
96 + <td class="text-center"><!-- Leechers --></td>
97 + <td class="text-center"><!-- Completed --></td>
98 + </tr>`;
99 +
100 + torrentList.insertAdjacentHTML('beforeend', html);
101 + }
102 +
31 103 /**
32 104 * Handler for copying download links (torrents)
33 105 */
@@ -458,6 +530,7 @@ function init() {
458 530 titleHandler();
459 531 descriptionHandler();
460 532 torrentListViewHandler();
533 + calculateTotalSize();
461 534 }
462 535
463 536 init();

Alex's Avatar Alex Thomassen revised this gist 2 years ago. Go to revision

1 file changed, 463 insertions

nyaa-shit.user.js(file created)

@@ -0,0 +1,463 @@
1 + // ==UserScript==
2 + // @name Nyaa - Personal Tweaks
3 + // @namespace github.com/Decicus
4 + // @match https://nyaa.si/*
5 + // @grant GM_setClipboard
6 + // @version 1.2.0
7 + // @author Decicus
8 + // @description Adds some extra functionality to Nyaa.
9 + // ==/UserScript==
10 +
11 + let copyAmount = 5;
12 +
13 + function getElements(linkType = 'download')
14 + {
15 + const elements = document.querySelectorAll(`.fa-${linkType}`);
16 + return Array.from(elements)
17 + .map(x => x.parentElement.href)
18 + .filter(x => x && x.trim() !== '');
19 + }
20 +
21 + function getLinks()
22 + {
23 + return getElements('download');
24 + }
25 +
26 + function getMagnets()
27 + {
28 + return getElements('magnet');
29 + }
30 +
31 + /**
32 + * Handler for copying download links (torrents)
33 + */
34 + function copyLinks(amount)
35 + {
36 + let links = getLinks();
37 + if (amount && typeof amount === 'number') {
38 + amount = amount > links.length ? links.length : amount;
39 + links = links.slice(0, amount);
40 + }
41 +
42 + const list = links.join('\n');
43 + GM_setClipboard(list);
44 + }
45 +
46 + /**
47 + * Handler for copying magnets
48 + */
49 + function copyMagnets(amount)
50 + {
51 + let magnets = getMagnets();
52 + if (amount && typeof amount === 'number') {
53 + amount = amount > magnets.length ? magnets.length : amount;
54 + magnets = magnets.slice(0, amount);
55 + }
56 +
57 + const list = magnets.join('\n');
58 + GM_setClipboard(list);
59 + }
60 +
61 + /**
62 + * Changes the download links for torrents/magnets to trigger a copy,
63 + * instead of default handling (download for torrents, torrent *client* for magnets).
64 + */
65 + function copyOnClick(ev)
66 + {
67 + ev.preventDefault();
68 +
69 + const target = ev.target;
70 + const isAnchor = target.nodeName === 'A';
71 + const parent = isAnchor ? target : target.parentElement;
72 + GM_setClipboard(parent.href + '\n');
73 +
74 + const checkIcon = 'fa-check';
75 + const originalIcon = parent.href.includes('/download/') ? 'fa-download' : 'fa-magnet';
76 +
77 + const iconElement = !target.classList.contains('fa') ? target.querySelector('.fa') : target;
78 + console.log(iconElement);
79 +
80 + if (iconElement) {
81 + iconElement.classList.replace(originalIcon, checkIcon);
82 +
83 + setTimeout(
84 + function() {
85 + iconElement.classList.replace(checkIcon, originalIcon);
86 + },
87 + 1500
88 + );
89 + }
90 + }
91 +
92 + /**
93 + * Changes said magnet/torrent links to use the `copyOnClick` handler.
94 + */
95 + function changeToCopy()
96 + {
97 + const links = document.querySelectorAll('.fa-magnet, .fa-download');
98 +
99 + for (const link of links)
100 + {
101 + link.parentElement.addEventListener('click', copyOnClick);
102 + }
103 + }
104 +
105 + /**
106 + * List of regex replacements so that I can copy titles as directory names, with my personally preferred naming scheme.
107 + */
108 + const titleReplacements = [
109 + {
110 + comment: 'Remove quoted series/movie title from the end',
111 + search: / \"[A-z0-9-!\s]+\"$/,
112 + replacement: '',
113 + },
114 + {
115 + comment: 'Move release group to the end',
116 + search: /^\[([-\w ]+)\] (.+)$/,
117 + replacement: '$2 [$1]',
118 + },
119 + {
120 + comment: 'Colon space to space dash space',
121 + search: /: /g,
122 + replacement: ' - ',
123 + },
124 + {
125 + comment: 'Season naming so Sonarr doesnt get confused because its dumb',
126 + search: / \(Season (\d+)\)/g,
127 + replacement: ' - S$1',
128 + },
129 + {
130 + comment: 'No slashes, please',
131 + search: /\//g,
132 + replacement: '-',
133 + },
134 + {
135 + comment: 'Closing => Opening bracket spacing between each "tag"',
136 + search: /\]\[/g,
137 + replacement: '] [',
138 + },
139 + {
140 + comment: 'Final replacement: Replace 2+ spaces with one space',
141 + search: /\s{2,}/g,
142 + replacement: ' ',
143 + },
144 + ];
145 +
146 + /**
147 + * Uses `titleReplacements` to "fix" torrent name when "Copy torrent name" button is clicked.
148 + */
149 + function fixTitle(input)
150 + {
151 + let finalString = input.trim();
152 + for (const replace of titleReplacements)
153 + {
154 + const { comment, search, replacement } = replace;
155 +
156 + const match = finalString.match(search);
157 + if (match === null) {
158 + continue;
159 + }
160 +
161 + console.log(`\nFound: ${search}\nComment: ${comment}\nMatches:\n- ${match.join('\n- ')}`);
162 + finalString = finalString.replace(search, replacement || '');
163 + }
164 +
165 + // Deal with any excess whitespace
166 + finalString = finalString.trim();
167 + console.log(`Original input: ${input}\nFinal string: ${finalString}`);
168 +
169 + return finalString;
170 + }
171 +
172 + /**
173 + * Adds copy button for torrent titles on torrent pages
174 + * Implements `fixTitle()`
175 + */
176 + function titleHandler()
177 + {
178 + const title = document.querySelector('h3.panel-title');
179 +
180 + if (!title) {
181 + return;
182 + }
183 +
184 + const titleParent = title.parentElement;
185 +
186 + const buttonParent = document.createElement('div');
187 + buttonParent.setAttribute('class', 'btn-group pull-right');
188 +
189 + const button = document.createElement('button');
190 + button.textContent = '📋';
191 + button.setAttribute('class', 'btn btn-primary btn-sm');
192 + button.addEventListener('click', function() {
193 + const origText = button.textContent;
194 +
195 + button.textContent = '✔';
196 + button.setAttribute('disabled', '1');
197 +
198 + const fixedTitle = fixTitle(title.textContent.trim());
199 + GM_setClipboard(fixedTitle);
200 +
201 + setTimeout(
202 + () => {
203 + button.textContent = origText;
204 + button.removeAttribute('disabled');
205 + }, 1500
206 + );
207 + });
208 +
209 + titleParent.classList.add('clearfix');
210 + buttonParent.insertAdjacentElement('afterbegin', button);
211 + titleParent.insertAdjacentElement('afterbegin', buttonParent);
212 + }
213 +
214 + function bbcodeConverter(html) {
215 + html = html.replace(/<pre(.*?)>(.*?)<\/pre>/gmi, "[code]$2[/code]");
216 + html = html.replace(/<h[1-7](.*?)>(.*?)<\/h[1-7]>/, "\n[h]$2[/h]\n");
217 + //paragraph handling:
218 + //- if a paragraph opens on the same line as another one closes, insert an extra blank line
219 + //- opening tag becomes two line breaks
220 + //- closing tags are just removed
221 + // html += html.replace(/<\/p><p/<\/p>\n<p/gi;
222 + // html += html.replace(/<p[^>]*>/\n\n/gi;
223 + // html += html.replace(/<\/p>//gi;
224 +
225 + html = html.replace(/<\/p>\n<p>/gi, "\n\n\n");
226 +
227 + html = html.replace(/<br(.*?)>/gi, "\n");
228 + html = html.replace(/<textarea(.*?)>(.*?)<\/textarea>/gmi, "\[code]$2\[\/code]");
229 + html = html.replace(/<b>/gi, "[b]");
230 + html = html.replace(/<i>/gi, "[i]");
231 + html = html.replace(/<u>/gi, "[u]");
232 + html = html.replace(/<\/b>/gi, "[/b]");
233 + html = html.replace(/<\/i>/gi, "[/i]");
234 + html = html.replace(/<\/u>/gi, "[/u]");
235 + html = html.replace(/<em>/gi, "[b]");
236 + html = html.replace(/<\/em>/gi, "[/b]");
237 + html = html.replace(/<strong>/gi, "[b]");
238 + html = html.replace(/<\/strong>/gi, "[/b]");
239 + html = html.replace(/<cite>/gi, "[i]");
240 + html = html.replace(/<\/cite>/gi, "[/i]");
241 + html = html.replace(/<font color="(.*?)">(.*?)<\/font>/gmi, "[color=$1]$2[/color]");
242 + html = html.replace(/<font color=(.*?)>(.*?)<\/font>/gmi, "[color=$1]$2[/color]");
243 + html = html.replace(/<link(.*?)>/gi, "");
244 + html = html.replace(/<li(.*?)>(.*?)<\/li>/gi, "[*]$2");
245 + html = html.replace(/<ul(.*?)>/gi, "[list]");
246 + html = html.replace(/<\/ul>/gi, "[/list]");
247 + html = html.replace(/<div>/gi, "\n");
248 + html = html.replace(/<\/div>/gi, "\n");
249 + html = html.replace(/<td(.*?)>/gi, " ");
250 + html = html.replace(/<tr(.*?)>/gi, "\n");
251 +
252 + html = html.replace(/<img(.*?)src="(.*?)"(.*?)>/gi, "[img]$2[/img]");
253 + html = html.replace(/<a(.*?)href="(.*?)"(.*?)>(.*?)<\/a>/gi, "[url=$2]$4[/url]");
254 +
255 + html = html.replace(/<head>(.*?)<\/head>/gmi, "");
256 + html = html.replace(/<object>(.*?)<\/object>/gmi, "");
257 + html = html.replace(/<script(.*?)>(.*?)<\/script>/gmi, "");
258 + html = html.replace(/<style(.*?)>(.*?)<\/style>/gmi, "");
259 + html = html.replace(/<title>(.*?)<\/title>/gmi, "");
260 + html = html.replace(/<!--(.*?)-->/gmi, "\n");
261 +
262 + html = html.replace(/\/\//gi, "/");
263 + html = html.replace(/http:\//gi, "http://");
264 +
265 + html = html.replace(/<(?:[^>'"]*|(['"]).*?\1)*>/gmi, "");
266 + html = html.replace(/\r\r/gi, "");
267 + html = html.replace(/\[img]\//gi, "[img]");
268 + html = html.replace(/\[url=\//gi, "[url=");
269 +
270 + html = html.replace(/(\S)\n/gi, "$1 ");
271 +
272 + return html;
273 + }
274 +
275 + function descriptionHandler()
276 + {
277 + const descriptionElem = document.querySelector('#torrent-description');
278 + if (!descriptionElem) {
279 + return;
280 + }
281 +
282 + console.log(bbcodeConverter(descriptionElem.innerHTML));
283 +
284 + const button = document.createElement('button');
285 + button.textContent = '📋 Copy description as BBCode';
286 + button.setAttribute('class', 'btn btn-primary');
287 + button.addEventListener('click', function() {
288 + const origText = button.textContent;
289 +
290 + button.textContent = '✔ Copied!';
291 + button.setAttribute('disabled', '1');
292 +
293 + const descriptionBBcode = bbcodeConverter(descriptionElem.innerHTML).trim();
294 + GM_setClipboard(descriptionBBcode);
295 +
296 + setTimeout(
297 + () => {
298 + button.textContent = origText;
299 + button.removeAttribute('disabled');
300 + }, 1500
301 + );
302 + });
303 +
304 + const panelHeading = document.createElement('div');
305 + panelHeading.setAttribute('class', 'panel-heading');
306 + panelHeading.insertAdjacentElement('beforeend', button);
307 +
308 + descriptionElem.insertAdjacentElement('beforebegin', panelHeading);
309 + }
310 +
311 + /**
312 + * Adds a clipboard icon (using Font Awesome 4) to each row that is marked for "mass copy".
313 + */
314 + function addClipboardIconToRow()
315 + {
316 + const rows = document.querySelectorAll('.torrent-list > tbody > tr > td[colspan="2"]');
317 +
318 + let currentAmount = 0;
319 +
320 + for (const row of rows)
321 + {
322 + currentAmount++;
323 + const iconExists = row.querySelector('a.clipboard-icon');
324 + if (iconExists) {
325 + iconExists.remove();
326 + }
327 +
328 + // Check if this row is past the point of "don't receive icon"
329 + if (currentAmount > copyAmount) {
330 + // `continue`, not `break` - If we break, then when we lower `copyAmount`, there will be extra rows with clipboard icon.
331 + continue;
332 + }
333 +
334 + row.insertAdjacentHTML('afterbegin', '<a href="#" class="comments clipboard-icon" style="margin-left: 0.5em;"><i class="fa fa-clipboard"></i></a>');
335 + }
336 + }
337 +
338 + /**
339 + * Adds buttons and number input fields above torrent table, for "mass copying" links/magnets.
340 + */
341 + function torrentListViewHandler()
342 + {
343 + const parentElement = document.querySelector('.table-responsive');
344 +
345 + if (!parentElement) {
346 + return;
347 + }
348 +
349 + addClipboardIconToRow();
350 +
351 + /**
352 + * Start: Copy all links/magnets
353 + */
354 + const element = document.createElement('button');
355 + element.innerHTML = '📋 Copy all download links <i class="fa fa-fw fa-download"></i>';
356 + element.setAttribute('class', 'btn btn-default');
357 + element.addEventListener('click', function() {
358 + const origText = element.innerHTML;
359 +
360 + element.innerHTML = '✔';
361 + element.setAttribute('disabled', '1');
362 + copyLinks();
363 +
364 + setTimeout(() => {
365 + element.innerHTML = origText;
366 + element.removeAttribute('disabled');
367 + }, 1500);
368 + });
369 +
370 + const magnetCopy = document.createElement('button');
371 + magnetCopy.innerHTML = '📋 Copy all magnets <i class="fa fa-fw fa-magnet"></i>';
372 + magnetCopy.setAttribute('class', 'btn btn-default');
373 + magnetCopy.addEventListener('click', function() {
374 + const origText = magnetCopy.innerHTML;
375 +
376 + magnetCopy.innerHTML = '✔';
377 + magnetCopy.setAttribute('disabled', '1');
378 + copyMagnets();
379 +
380 + setTimeout(() => {
381 + magnetCopy.innerHTML = origText;
382 + magnetCopy.removeAttribute('disabled');
383 + }, 1500);
384 + });
385 +
386 + /**
387 + * End: Copy all links/magnets
388 + */
389 +
390 + /**
391 + * Start: Copy X links/magnets
392 + */
393 + const linkAmount = (getLinks()).length;
394 + copyAmount = linkAmount < 5 ? linkAmount : 5;
395 + const amountSelect = document.createElement('input');
396 + amountSelect.setAttribute('type', 'number');
397 + amountSelect.setAttribute('min', '2');
398 + amountSelect.setAttribute('max', linkAmount);
399 + amountSelect.setAttribute('value', copyAmount);
400 + amountSelect.setAttribute('class', 'pull-right');
401 +
402 + const amountCopyLinks = document.createElement('button');
403 + amountCopyLinks.innerHTML = `📋 Copy ${copyAmount} download links <i class="fa fa-fw fa-download"></i>`;
404 + amountCopyLinks.setAttribute('class', 'btn btn-default pull-right');
405 + amountCopyLinks.addEventListener('click', function() {
406 + const origText = amountCopyLinks.innerHTML;
407 +
408 + amountCopyLinks.innerHTML = '✔';
409 + amountCopyLinks.setAttribute('disabled', '1');
410 + copyLinks(copyAmount);
411 +
412 + setTimeout(() => {
413 + amountCopyLinks.innerHTML = origText;
414 + amountCopyLinks.removeAttribute('disabled');
415 + }, 1500);
416 + });
417 +
418 + const amountCopyMagnets = document.createElement('button');
419 + amountCopyMagnets.innerHTML = `📋 Copy ${copyAmount} magnets <i class="fa fa-fw fa-magnet"></i>`;
420 + amountCopyMagnets.setAttribute('class', 'btn btn-default pull-right');
421 + amountCopyMagnets.addEventListener('click', function() {
422 + const origText = amountCopyMagnets.innerHTML;
423 +
424 + amountCopyMagnets.innerHTML = '✔';
425 + amountCopyMagnets.setAttribute('disabled', '1');
426 + copyMagnets(copyAmount);
427 +
428 + setTimeout(() => {
429 + amountCopyMagnets.innerHTML = origText;
430 + amountCopyMagnets.removeAttribute('disabled');
431 + }, 1500);
432 + });
433 +
434 + amountSelect.addEventListener('change', function() {
435 + copyAmount = parseInt(amountSelect.value, 10);
436 + amountCopyLinks.innerHTML = `📋 Copy ${copyAmount} download links <i class="fa fa-fw fa-download"></i>`;
437 + amountCopyMagnets.innerHTML = `📋 Copy ${copyAmount} magnets <i class="fa fa-fw fa-magnet"></i>`;
438 +
439 + addClipboardIconToRow();
440 + });
441 + /**
442 + * End: Copy X links/magnets
443 + */
444 +
445 + /**
446 + * Append all these buttons and inputs to the DOM.
447 + */
448 + parentElement.insertAdjacentElement('beforebegin', element);
449 + parentElement.insertAdjacentElement('beforebegin', magnetCopy);
450 +
451 + parentElement.insertAdjacentElement('beforebegin', amountSelect);
452 + parentElement.insertAdjacentElement('beforebegin', amountCopyMagnets);
453 + parentElement.insertAdjacentElement('beforebegin', amountCopyLinks);
454 + }
455 +
456 + function init() {
457 + changeToCopy();
458 + titleHandler();
459 + descriptionHandler();
460 + torrentListViewHandler();
461 + }
462 +
463 + init();
Newer Older