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
18 days 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})();