JAVJunkies Torrent to Magnet Converter

Convierte archivos torrent en enlaces magnet y los muestra debajo de las imágenes

Size

18.5 KB

Version

1.1.3

Created

Oct 27, 2025

Updated

about 1 month ago

1// ==UserScript==
2// @name		JAVJunkies Torrent to Magnet Converter
3// @description		Convierte archivos torrent en enlaces magnet y los muestra debajo de las imágenes
4// @version		1.1.3
5// @match		https://*.javjunkies.org/*
6// @icon		https://javjunkies.org/main/favicon.ico
7// @grant		GM.xmlhttpRequest
8// ==/UserScript==
9(function() {
10    'use strict';
11
12    console.log('JAVJunkies Torrent to Magnet Converter iniciado');
13
14    // Implementación de SHA1
15    function sha1(msg) {
16        function rotateLeft(n, s) {
17            return (n << s) | (n >>> (32 - s));
18        }
19
20        function toHexStr(n) {
21            let s = "", v;
22            for (let i = 7; i >= 0; i--) {
23                v = (n >>> (i * 4)) & 0xf;
24                s += v.toString(16);
25            }
26            return s;
27        }
28
29        function utf8Encode(str) {
30            return unescape(encodeURIComponent(str));
31        }
32
33        let H0 = 0x67452301;
34        let H1 = 0xEFCDAB89;
35        let H2 = 0x98BADCFE;
36        let H3 = 0x10325476;
37        let H4 = 0xC3D2E1F0;
38
39        let i, temp;
40        msg = utf8Encode(msg);
41        let msgLen = msg.length;
42        let wordArray = [];
43
44        for (i = 0; i < msgLen - 3; i += 4) {
45            wordArray.push(
46                msg.charCodeAt(i) << 24 |
47                msg.charCodeAt(i + 1) << 16 |
48                msg.charCodeAt(i + 2) << 8 |
49                msg.charCodeAt(i + 3)
50            );
51        }
52
53        switch (msgLen % 4) {
54            case 0:
55                i = 0x080000000;
56                break;
57            case 1:
58                i = msg.charCodeAt(msgLen - 1) << 24 | 0x0800000;
59                break;
60            case 2:
61                i = msg.charCodeAt(msgLen - 2) << 24 | msg.charCodeAt(msgLen - 1) << 16 | 0x08000;
62                break;
63            case 3:
64                i = msg.charCodeAt(msgLen - 3) << 24 | msg.charCodeAt(msgLen - 2) << 16 | msg.charCodeAt(msgLen - 1) << 8 | 0x80;
65                break;
66        }
67
68        wordArray.push(i);
69
70        while ((wordArray.length % 16) != 14) {
71            wordArray.push(0);
72        }
73
74        wordArray.push(msgLen >>> 29);
75        wordArray.push((msgLen << 3) & 0x0ffffffff);
76
77        let W = new Array(80);
78        let A, B, C, D, E;
79
80        for (let blockStart = 0; blockStart < wordArray.length; blockStart += 16) {
81            for (i = 0; i < 16; i++) {
82                W[i] = wordArray[blockStart + i];
83            }
84            for (i = 16; i <= 79; i++) {
85                W[i] = rotateLeft(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
86            }
87
88            A = H0;
89            B = H1;
90            C = H2;
91            D = H3;
92            E = H4;
93
94            for (i = 0; i <= 19; i++) {
95                temp = (rotateLeft(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
96                E = D;
97                D = C;
98                C = rotateLeft(B, 30);
99                B = A;
100                A = temp;
101            }
102
103            for (i = 20; i <= 39; i++) {
104                temp = (rotateLeft(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
105                E = D;
106                D = C;
107                C = rotateLeft(B, 30);
108                B = A;
109                A = temp;
110            }
111
112            for (i = 40; i <= 59; i++) {
113                temp = (rotateLeft(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
114                E = D;
115                D = C;
116                C = rotateLeft(B, 30);
117                B = A;
118                A = temp;
119            }
120
121            for (i = 60; i <= 79; i++) {
122                temp = (rotateLeft(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
123                E = D;
124                D = C;
125                C = rotateLeft(B, 30);
126                B = A;
127                A = temp;
128            }
129
130            H0 = (H0 + A) & 0x0ffffffff;
131            H1 = (H1 + B) & 0x0ffffffff;
132            H2 = (H2 + C) & 0x0ffffffff;
133            H3 = (H3 + D) & 0x0ffffffff;
134            H4 = (H4 + E) & 0x0ffffffff;
135        }
136
137        return (toHexStr(H0) + toHexStr(H1) + toHexStr(H2) + toHexStr(H3) + toHexStr(H4)).toLowerCase();
138    }
139
140    // Implementación de bencode decoder
141    const bencode = {
142        decode: function(buffer) {
143            const data = new Uint8Array(buffer);
144            let position = 0;
145            let iterations = 0;
146            const MAX_ITERATIONS = 1000000; // Límite de seguridad
147
148            function readByte() {
149                if (position >= data.length) {
150                    throw new Error('Unexpected end of data');
151                }
152                return data[position++];
153            }
154
155            function readUntil(char) {
156                const start = position;
157                while (position < data.length && data[position] !== char) {
158                    position++;
159                    if (++iterations > MAX_ITERATIONS) {
160                        throw new Error('Too many iterations - possible infinite loop');
161                    }
162                }
163                if (position >= data.length) {
164                    throw new Error('Unexpected end of data while looking for delimiter');
165                }
166                return data.slice(start, position);
167            }
168
169            function decodeNext() {
170                if (++iterations > MAX_ITERATIONS) {
171                    throw new Error('Too many iterations - possible infinite loop');
172                }
173                
174                const byte = readByte();
175
176                // Integer
177                if (byte === 105) { // 'i'
178                    const numBytes = readUntil(101); // until 'e'
179                    position++; // skip 'e'
180                    const numStr = String.fromCharCode.apply(null, numBytes);
181                    return parseInt(numStr, 10);
182                }
183
184                // List
185                if (byte === 108) { // 'l'
186                    const list = [];
187                    while (data[position] !== 101) { // until 'e'
188                        list.push(decodeNext());
189                    }
190                    position++; // skip 'e'
191                    return list;
192                }
193
194                // Dictionary
195                if (byte === 100) { // 'd'
196                    const dict = {};
197                    while (data[position] !== 101) { // until 'e'
198                        const key = decodeNext();
199                        const keyStr = typeof key === 'string' ? key : String.fromCharCode.apply(null, key);
200                        dict[keyStr] = decodeNext();
201                    }
202                    position++; // skip 'e'
203                    return dict;
204                }
205
206                // String/Bytes
207                position--; // go back one byte
208                const lengthBytes = readUntil(58); // until ':'
209                position++; // skip ':'
210                const lengthStr = String.fromCharCode.apply(null, lengthBytes);
211                const length = parseInt(lengthStr, 10);
212                
213                if (isNaN(length) || length < 0) {
214                    throw new Error('Invalid string length');
215                }
216                
217                if (position + length > data.length) {
218                    throw new Error('String length exceeds buffer size');
219                }
220                
221                const bytes = data.slice(position, position + length);
222                position += length;
223                return bytes;
224            }
225
226            return decodeNext();
227        },
228
229        encode: function(obj) {
230            function encodeValue(val) {
231                // Integer
232                if (typeof val === 'number') {
233                    return stringToBytes('i' + val + 'e');
234                }
235
236                // String/Bytes
237                if (val instanceof Uint8Array) {
238                    const lengthBytes = stringToBytes(val.length + ':');
239                    const result = new Uint8Array(lengthBytes.length + val.length);
240                    result.set(lengthBytes);
241                    result.set(val, lengthBytes.length);
242                    return result;
243                }
244
245                // Array
246                if (Array.isArray(val)) {
247                    const parts = [stringToBytes('l')];
248                    for (const item of val) {
249                        parts.push(encodeValue(item));
250                    }
251                    parts.push(stringToBytes('e'));
252                    return concatBytes(parts);
253                }
254
255                // Object
256                if (typeof val === 'object') {
257                    const parts = [stringToBytes('d')];
258                    const keys = Object.keys(val).sort();
259                    for (const key of keys) {
260                        parts.push(encodeValue(stringToBytes(key)));
261                        parts.push(encodeValue(val[key]));
262                    }
263                    parts.push(stringToBytes('e'));
264                    return concatBytes(parts);
265                }
266
267                throw new Error('Unsupported type');
268            }
269
270            function stringToBytes(str) {
271                const bytes = new Uint8Array(str.length);
272                for (let i = 0; i < str.length; i++) {
273                    bytes[i] = str.charCodeAt(i);
274                }
275                return bytes;
276            }
277
278            function concatBytes(arrays) {
279                const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
280                const result = new Uint8Array(totalLength);
281                let offset = 0;
282                for (const arr of arrays) {
283                    result.set(arr, offset);
284                    offset += arr.length;
285                }
286                return result;
287            }
288
289            return encodeValue(obj);
290        }
291    };
292
293    // Función para convertir Uint8Array a string binario
294    function uint8ArrayToString(bytes) {
295        let binary = '';
296        for (let i = 0; i < bytes.length; i++) {
297            binary += String.fromCharCode(bytes[i]);
298        }
299        return binary;
300    }
301
302    // Función para convertir torrent a magnet
303    async function torrentToMagnet(torrentData) {
304        try {
305            console.log('Decodificando torrent... Tamaño:', torrentData.byteLength);
306            // Decodificar el archivo torrent
307            const decoded = bencode.decode(torrentData);
308            
309            console.log('Torrent decodificado, obteniendo info hash...');
310            console.log('Claves del torrent:', Object.keys(decoded));
311            
312            // Obtener el info hash
313            const info = decoded.info;
314            if (!info) {
315                throw new Error('No se encontró la sección info en el torrent');
316            }
317            
318            console.log('Codificando info para hash...');
319            const encodedInfo = bencode.encode(info);
320            console.log('Info codificado, calculando hash...');
321            const infoHash = sha1(uint8ArrayToString(encodedInfo));
322            
323            // Obtener el nombre
324            const nameBytes = info.name;
325            const name = new TextDecoder('utf-8').decode(nameBytes);
326            
327            console.log('Nombre del torrent:', name);
328            console.log('Info hash:', infoHash);
329            
330            // Construir el enlace magnet
331            let magnetLink = `magnet:?xt=urn:btih:${infoHash}`;
332            magnetLink += `&dn=${encodeURIComponent(name)}`;
333            
334            // Agregar trackers si existen
335            if (decoded.announce) {
336                const announceUrl = new TextDecoder('utf-8').decode(decoded.announce);
337                magnetLink += `&tr=${encodeURIComponent(announceUrl)}`;
338            }
339            
340            if (decoded['announce-list']) {
341                for (const tierArray of decoded['announce-list']) {
342                    for (const tracker of tierArray) {
343                        const trackerUrl = new TextDecoder('utf-8').decode(tracker);
344                        magnetLink += `&tr=${encodeURIComponent(trackerUrl)}`;
345                    }
346                }
347            }
348            
349            return { magnetLink, name };
350        } catch (error) {
351            console.error('Error al convertir torrent a magnet:', error);
352            console.error('Stack:', error.stack);
353            throw error;
354        }
355    }
356
357    // Función para descargar el archivo torrent
358    async function downloadTorrent(fileParam) {
359        return new Promise((resolve, reject) => {
360            const url = `https://javjunkies.org/main/Jl.php?kEy=2521&file=${fileParam}`;
361            
362            console.log('Descargando torrent desde:', url);
363            
364            GM.xmlhttpRequest({
365                method: 'GET',
366                url: url,
367                responseType: 'arraybuffer',
368                onload: function(response) {
369                    console.log('Respuesta recibida, status:', response.status);
370                    if (response.status === 200) {
371                        resolve(response.response);
372                    } else {
373                        reject(new Error(`Error al descargar: ${response.status}`));
374                    }
375                },
376                onerror: function(error) {
377                    console.error('Error en la petición:', error);
378                    reject(error);
379                }
380            });
381        });
382    }
383
384    // Función para crear el enlace magnet debajo de la imagen
385    function createMagnetLink(imageDiv, magnetLink, name) {
386        // Verificar si ya existe un enlace magnet
387        if (imageDiv.querySelector('.magnet-link-container')) {
388            return;
389        }
390
391        const container = document.createElement('div');
392        container.className = 'magnet-link-container';
393        container.style.cssText = 'margin-top: 10px; padding: 10px; background-color: #f0f0f0; border-radius: 5px; border: 1px solid #ddd;';
394        
395        const title = document.createElement('div');
396        title.style.cssText = 'font-weight: bold; margin-bottom: 5px; color: #333; font-size: 12px;';
397        title.textContent = name;
398        
399        const link = document.createElement('a');
400        link.href = magnetLink;
401        link.textContent = '🧲 Abrir con cliente torrent';
402        link.style.cssText = 'color: #0066cc; text-decoration: none; font-size: 14px; display: inline-block; padding: 5px 10px; background-color: #e6f2ff; border-radius: 3px; margin-right: 10px;';
403        link.onmouseover = function() { this.style.backgroundColor = '#cce5ff'; };
404        link.onmouseout = function() { this.style.backgroundColor = '#e6f2ff'; };
405        
406        const copyButton = document.createElement('button');
407        copyButton.textContent = '📋 Copiar enlace';
408        copyButton.style.cssText = 'color: #0066cc; background-color: #e6f2ff; border: 1px solid #0066cc; border-radius: 3px; padding: 5px 10px; cursor: pointer; font-size: 14px;';
409        copyButton.onmouseover = function() { this.style.backgroundColor = '#cce5ff'; };
410        copyButton.onmouseout = function() { this.style.backgroundColor = '#e6f2ff'; };
411        copyButton.onclick = function() {
412            navigator.clipboard.writeText(magnetLink).then(() => {
413                copyButton.textContent = '✅ Copiado!';
414                setTimeout(() => {
415                    copyButton.textContent = '📋 Copiar enlace';
416                }, 2000);
417            });
418        };
419        
420        container.appendChild(title);
421        container.appendChild(link);
422        container.appendChild(copyButton);
423        
424        imageDiv.appendChild(container);
425    }
426
427    // Función para procesar una imagen
428    async function processImage(imageDiv) {
429        const link = imageDiv.querySelector('a[onclick]');
430        if (!link) return;
431        
432        const onclick = link.getAttribute('onclick');
433        const match = onclick.match(/JOpen\('&file=([^']+)'\)/);
434        
435        if (!match) return;
436        
437        const fileParam = match[1];
438        
439        console.log('Procesando imagen con file param:', fileParam);
440        
441        // Agregar indicador de carga
442        const loadingDiv = document.createElement('div');
443        loadingDiv.className = 'magnet-link-container';
444        loadingDiv.style.cssText = 'margin-top: 10px; padding: 10px; background-color: #fff3cd; border-radius: 5px; border: 1px solid #ffc107; color: #856404;';
445        loadingDiv.textContent = '⏳ Generando enlace magnet...';
446        imageDiv.appendChild(loadingDiv);
447        
448        try {
449            // Descargar el torrent
450            const torrentData = await downloadTorrent(fileParam);
451            
452            // Convertir a magnet
453            const { magnetLink, name } = await torrentToMagnet(torrentData);
454            
455            // Remover indicador de carga
456            loadingDiv.remove();
457            
458            // Crear el enlace magnet
459            createMagnetLink(imageDiv, magnetLink, name);
460            
461            console.log('Enlace magnet generado exitosamente:', name);
462        } catch (error) {
463            console.error('Error al procesar imagen:', error);
464            loadingDiv.textContent = '❌ Error al generar enlace magnet';
465            loadingDiv.style.backgroundColor = '#f8d7da';
466            loadingDiv.style.borderColor = '#f5c6cb';
467            loadingDiv.style.color = '#721c24';
468        }
469    }
470
471    // Función para procesar todas las imágenes
472    async function processAllImages() {
473        const imageDivs = document.querySelectorAll('div.image');
474        console.log(`Encontradas ${imageDivs.length} imágenes`);
475        
476        for (const imageDiv of imageDivs) {
477            await processImage(imageDiv);
478            // Pequeña pausa entre cada procesamiento
479            await new Promise(resolve => setTimeout(resolve, 500));
480        }
481    }
482
483    // Función de inicialización
484    function init() {
485        // Esperar a que el DOM esté listo
486        if (document.readyState === 'loading') {
487            document.addEventListener('DOMContentLoaded', processAllImages);
488        } else {
489            processAllImages();
490        }
491        
492        // Observar cambios en el DOM para páginas dinámicas
493        const observer = new MutationObserver((mutations) => {
494            for (const mutation of mutations) {
495                if (mutation.addedNodes.length > 0) {
496                    const newImages = document.querySelectorAll('div.image:not(:has(.magnet-link-container))');
497                    if (newImages.length > 0) {
498                        console.log(`Nuevas imágenes detectadas: ${newImages.length}`);
499                        newImages.forEach(processImage);
500                    }
501                }
502            }
503        });
504        
505        observer.observe(document.body, {
506            childList: true,
507            subtree: true
508        });
509    }
510
511    // Iniciar el script
512    init();
513})();
JAVJunkies Torrent to Magnet Converter | Robomonkey