Last active 1753547450

Miscelleanous tweaks applied to Nyaa.si

Alex's Avatar Alex revised this gist 1753547450. Go to revision

No changes

Alex's Avatar Alex Thomassen revised this gist 1753547251. 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 1724089436. 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 1697116449. 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 1693418580. 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 1693393453. 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 1693393422. 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 1691680570. 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