PyBrid Auto Converter

Automatically convert magnet links through PyBrid

// ==UserScript==
// @name      PyBrid Auto Converter
// @namespace PybridMagnetListener
// @version   4.0
// @description  Automatically convert magnet links through PyBrid
// @author       harryeffinpotter
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @license      GPL-3.0-only
// @connect      *
// @connect      https://pydrive.harryeffingpotter.com
// @run-at       document-start
// ==/UserScript==

/* jshint esversion: 11 */

(function() {
    'use strict';

    // Configuration
    const PYBRID_URL = 'https://pydrive.harryeffingpotter.com'; // UPDATE THIS WITH YOUR ACTUAL URL

    // Account management
    async function getCredentials() {
        const username = GM_getValue('pybrid_username', null);
        const password = GM_getValue('pybrid_password', null);
        return username && password ? { username, password } : null;
    }

    async function saveCredentials(username, password) {
        await GM_setValue('pybrid_username', username);
        await GM_setValue('pybrid_password', password);
    }

    async function promptForCredentials() {
        return new Promise((resolve) => {
            // Create modal overlay
            const overlay = document.createElement('div');
            overlay.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.5);
                z-index: 999999;
                display: flex;
                align-items: center;
                justify-content: center;
            `;

            // Create modal
            const modal = document.createElement('div');
            modal.style.cssText = `
                background: white;
                padding: 30px;
                border-radius: 8px;
                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
                max-width: 400px;
                width: 90%;
            `;

            modal.innerHTML = `
                <h2 style="margin: 0 0 20px 0; color: #333;">PyBrid Login Required</h2>
                <div style="margin-bottom: 15px;">
                    <label style="display: block; margin-bottom: 5px; color: #555;">Username:</label>
                    <input type="text" id="pybrid-username" style="
                        width: 100%;
                        padding: 10px;
                        border: 1px solid #ddd;
                        border-radius: 4px;
                        font-size: 16px;
                        box-sizing: border-box;
                    ">
                </div>
                <div style="margin-bottom: 20px;">
                    <label style="display: block; margin-bottom: 5px; color: #555;">Password:</label>
                    <div style="position: relative;">
                        <input type="password" id="pybrid-password" style="
                            width: 100%;
                            padding: 10px;
                            padding-right: 40px;
                            border: 1px solid #ddd;
                            border-radius: 4px;
                            font-size: 16px;
                            box-sizing: border-box;
                        ">
                        <button type="button" id="toggle-password" style="
                            position: absolute;
                            right: 10px;
                            top: 50%;
                            transform: translateY(-50%);
                            background: none;
                            border: none;
                            cursor: pointer;
                            padding: 5px;
                            color: #666;
                        ">
                            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path id="eye-open" d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
                                <circle id="eye-pupil" cx="12" cy="12" r="3"></circle>
                                <path id="eye-closed" d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24M1 1l22 22" style="display: none;"></path>
                            </svg>
                        </button>
                    </div>
                </div>
                <div style="display: flex; gap: 10px; justify-content: flex-end;">
                    <button id="cancel-btn" style="
                        padding: 10px 20px;
                        border: 1px solid #ddd;
                        background: white;
                        color: #666;
                        border-radius: 4px;
                        cursor: pointer;
                        font-size: 16px;
                    ">Cancel</button>
                    <button id="login-btn" style="
                        padding: 10px 20px;
                        border: none;
                        background: #5DADE2;
                        color: white;
                        border-radius: 4px;
                        cursor: pointer;
                        font-size: 16px;
                    ">Login</button>
                </div>
            `;

            overlay.appendChild(modal);
            document.body.appendChild(overlay);

            // Focus username field
            const usernameInput = modal.querySelector('#pybrid-username');
            const passwordInput = modal.querySelector('#pybrid-password');
            usernameInput.focus();

            // Toggle password visibility
            const toggleBtn = modal.querySelector('#toggle-password');
            const eyeOpen = modal.querySelector('#eye-open');
            const eyePupil = modal.querySelector('#eye-pupil');
            const eyeClosed = modal.querySelector('#eye-closed');

            toggleBtn.addEventListener('click', () => {
                if (passwordInput.type === 'password') {
                    passwordInput.type = 'text';
                    eyeOpen.style.display = 'none';
                    eyePupil.style.display = 'none';
                    eyeClosed.style.display = 'block';
                } else {
                    passwordInput.type = 'password';
                    eyeOpen.style.display = 'block';
                    eyePupil.style.display = 'block';
                    eyeClosed.style.display = 'none';
                }
            });

            // Handle login
            const handleLogin = async () => {
                const username = usernameInput.value.trim();
                const password = passwordInput.value;

                if (username && password) {
                    await saveCredentials(username, password);
                    document.body.removeChild(overlay);
                    resolve({ username, password });
                }
            };

            // Handle cancel
            const handleCancel = () => {
                document.body.removeChild(overlay);
                resolve(null);
            };

            // Button clicks
            modal.querySelector('#login-btn').addEventListener('click', handleLogin);
            modal.querySelector('#cancel-btn').addEventListener('click', handleCancel);

            // Enter key submits
            usernameInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') passwordInput.focus();
            });
            passwordInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') handleLogin();
            });

            // Escape key cancels
            overlay.addEventListener('keydown', (e) => {
                if (e.key === 'Escape') handleCancel();
            });
        });
    }

    // Convert a magnet link
    async function convertMagnet(magnetUrl, retryCount = 0) {
        // Get saved credentials or prompt for them
        let credentials = await getCredentials();
        if (!credentials) {
            credentials = await promptForCredentials();
            if (!credentials) {
                alert('Cannot convert without login credentials.');
                return null;
            }
        }

        const formData = new FormData();
        formData.append('link_0', magnetUrl);
        formData.append('source', 'tampermonkey');
        formData.append('username', credentials.username);
        formData.append('password', credentials.password);

        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: `${PYBRID_URL}/convert`,
                data: formData,
                onload: async function(response) {
                    if (response.status === 200 || response.status === 303) {
                        // Success - get the redirect URL
                        const redirectUrl = response.finalUrl || response.responseHeaders.match(/location: (.+)/i)?.[1];
                        if (redirectUrl) {
                            window.open(redirectUrl, '_blank');
                            resolve(true);
                        } else {
                            alert('Conversion succeeded but no redirect URL found');
                            resolve(false);
                        }
                    } else if (response.status === 401 && retryCount === 0) {
                        // Invalid credentials - clear and retry once
                        await GM_deleteValue('pybrid_username');
                        await GM_deleteValue('pybrid_password');
                        alert('Invalid username or password. Please login again.');
                        resolve(await convertMagnet(magnetUrl, 1));
                    } else if (response.status === 429) {
                        alert('Rate limit exceeded. Please wait a moment and try again.');
                        resolve(false);
                    } else {
                        alert(`Conversion failed: ${response.status}`);
                        resolve(false);
                    }
                },
                onerror: function(error) {
                    console.error('PyBrid conversion error:', error);
                    alert('Network error. This usually means:\n\n' +
                          '1. Tampermonkey blocked the connection\n' +
                          '2. Wrong PyBrid URL in script\n' +
                          '3. PyBrid server is down\n\n' +
                          'Check browser console (F12) for details.');
                    resolve(false);
                }
            });
        });
    }

    // Intercept all clicks
    function interceptClicks(e) {
        let target = e.target;

        // Check if clicked element or any parent is a link
        while (target && target !== document.body) {
            if (target.tagName === 'A' && target.href) {
                const href = target.href;

                // Check if it's a magnet link
                if (href.startsWith('magnet:')) {
                    e.preventDefault();
                    e.stopPropagation();
                    e.stopImmediatePropagation();

                    // Convert the magnet link
                    convertMagnet(href);
                    return false;
                }
            }
            target = target.parentElement;
        }
    }

    // Set up interceptors as early as possible
    document.addEventListener('click', interceptClicks, true);

    // Also intercept dynamically added links
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            mutation.addedNodes.forEach((node) => {
                if (node.nodeType === 1) { // Element node
                    // Check if it's a link or contains links
                    if (node.tagName === 'A' && node.href && node.href.startsWith('magnet:')) {
                        node.addEventListener('click', interceptClicks, true);
                    } else if (node.querySelectorAll) {
                        node.querySelectorAll('a[href^="magnet:"]').forEach(link => {
                            link.addEventListener('click', interceptClicks, true);
                        });
                    }
                }
            });
        });
    });

    // Start observing when DOM is ready
    if (document.body) {
        observer.observe(document.body, { childList: true, subtree: true });
    } else {
        document.addEventListener('DOMContentLoaded', () => {
            observer.observe(document.body, { childList: true, subtree: true });
        });
    }

})();