Music Manager - Universal Sync

Press '/' for menu. Songs stay saved across ALL different websites! Now with search + hover URL scanner that auto-copies.

Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greasyfork.org/scripts/579897/1835463/Music%20Manager%20-%20Universal%20Sync.js

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Music Manager - Universal Sync
// @namespace    http://tampermonkey.net/
// @version      12.0
// @description  Press '/' for menu. Songs stay saved across ALL different websites! Now with search + hover URL scanner that auto-copies.
// @author       Gemini
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // ---- CONFIG ----
    const defaultSongs = [
        { name: "Arctic Monkeys - 505", url: "https://youtu.be/CKI8iQTgZKU?si=O3Z_qmJTGLECGhCa", fav: true, vid: false },
        { name: "The Neighbourhood - Softcore", url: "https://youtu.be/Ssj5ZBuhwIo?si=h4fTut2Vq7IM1QFC", fav: false, vid: false }
    ];

    let savedSongs = GM_getValue('tm_universal_playlist', defaultSongs);
    let currentTab = 'library';
    let searchQuery = '';
    let scannerEnabled = true;

    // ---- UNIVERSAL URL SCANNER (hovers over websites & auto-copies) ----
    const scannerBox = document.createElement('div');
    Object.assign(scannerBox.style, {
        position: 'fixed',
        display: 'none',
        background: '#0d0d0d',
        border: '1px solid #ff3333',
        borderRadius: '8px',
        padding: '8px 14px',
        color: '#00ff88',
        fontFamily: 'monospace',
        fontSize: '12px',
        zIndex: '999999',
        pointerEvents: 'none',
        maxWidth: '600px',
        overflow: 'hidden',
        textOverflow: 'ellipsis',
        whiteSpace: 'nowrap',
        boxShadow: '0 4px 20px rgba(255,0,0,0.3)',
        transition: 'opacity 0.1s'
    });
    document.body.appendChild(scannerBox);

    // Scanner status indicator in corner
    const scannerStatus = document.createElement('div');
    Object.assign(scannerStatus.style, {
        position: 'fixed',
        bottom: '10px',
        right: '10px',
        background: '#0d0d0d',
        border: '1px solid #00ff88',
        borderRadius: '6px',
        padding: '4px 10px',
        color: '#00ff88',
        fontFamily: 'monospace',
        fontSize: '10px',
        zIndex: '999999',
        cursor: 'pointer',
        userSelect: 'none',
        opacity: '0.6'
    });
    scannerStatus.textContent = '🔍 SCANNER ON';
    scannerStatus.title = 'Click to toggle URL scanner on/off';
    scannerStatus.onclick = () => {
        scannerEnabled = !scannerEnabled;
        scannerStatus.textContent = scannerEnabled ? '🔍 SCANNER ON' : '🔍 SCANNER OFF';
        scannerStatus.style.borderColor = scannerEnabled ? '#00ff88' : '#ff3333';
        scannerStatus.style.color = scannerEnabled ? '#00ff88' : '#ff3333';
        if (!scannerEnabled) scannerBox.style.display = 'none';
    };
    document.body.appendChild(scannerStatus);

    // Track last copied URL to avoid spam
    let lastCopiedUrl = '';

    function positionScannerBox(e) {
        let x = e.clientX + 16;
        let y = e.clientY + 16;
        const rect = scannerBox.getBoundingClientRect();
        if (x + rect.width > window.innerWidth - 10) x = e.clientX - rect.width - 16;
        if (y + rect.height > window.innerHeight - 10) y = e.clientY - rect.height - 16;
        if (x < 10) x = 10;
        if (y < 10) y = 10;
        scannerBox.style.left = x + 'px';
        scannerBox.style.top = y + 'px';
    }

    // Hover listener on the whole document
    document.addEventListener('mousemove', (e) => {
        if (!scannerEnabled) return;

        const target = e.target;
        let url = null;

        // Check if hovering over a link
        if (target.tagName === 'A' && target.href) {
            url = target.href;
        }
        // Check if hovering over an image
        else if (target.tagName === 'IMG' && target.src) {
            url = target.src;
        }
        // Check for parent anchors (in case inner elements like spans inside <a>)
        else {
            let parent = target.closest('a');
            if (parent && parent.href) {
                url = parent.href;
            }
            // Check for elements with data-url or similar attributes
            if (!url && target.dataset && target.dataset.url) {
                url = target.dataset.url;
            }
        }

        if (url) {
            // Show the URL
            scannerBox.textContent = '📍 ' + url;
            scannerBox.style.display = 'block';
            positionScannerBox(e);

            // Auto-copy to clipboard (only if it's a new URL)
            if (url !== lastCopiedUrl) {
                lastCopiedUrl = url;
                navigator.clipboard.writeText(url).catch(() => {
                    // Fallback for older browsers
                    const ta = document.createElement('textarea');
                    ta.value = url;
                    ta.style.position = 'fixed';
                    ta.style.opacity = '0';
                    document.body.appendChild(ta);
                    ta.select();
                    document.execCommand('copy');
                    document.body.removeChild(ta);
                });
                // Flash effect
                scannerBox.style.borderColor = '#00ff88';
                setTimeout(() => { scannerBox.style.borderColor = '#ff3333'; }, 200);
            }
        } else {
            scannerBox.style.display = 'none';
        }
    });

    // Clean up when leaving the page
    document.addEventListener('mouseleave', () => {
        if (scannerEnabled) scannerBox.style.display = 'none';
    });

    // ---- INFO MODAL (Draggable) ----
    const infoBox = document.createElement('div');
    Object.assign(infoBox.style, {
        position: 'fixed', top: '50%', left: '50%',
        transform: 'translate(-50%, -50%)',
        background: '#1e1e1e', border: '1px solid #444', borderRadius: '10px',
        zIndex: '10001', display: 'none', flexDirection: 'column',
        width: '300px', padding: '0 0 20px 0',
        boxShadow: '0 0 50px rgba(0,0,0,0.9)', fontFamily: 'Segoe UI, sans-serif',
        color: 'white', textAlign: 'center', overflow: 'hidden'
    });
    document.body.appendChild(infoBox);

    function showInfo(song) {
        infoBox.innerHTML = `
            <div id="infoDragHandle" style="background: #333; color: #ff0000; padding: 8px; cursor: grab; font-weight: bold; font-size: 12px; margin-bottom: 15px; user-select: none;">
                :: DETAILS (Drag) ::
            </div>
            <div style="padding: 0 20px;">
                <div style="font-size: 11px; color: #aaa;">NAME</div>
                <div style="font-size: 13px; margin-bottom: 15px; word-break: break-all;">${song.name}</div>
                <div style="font-size: 11px; color: #aaa;">URL</div>
                <div style="font-size: 12px; margin-bottom: 20px; word-break: break-all; color: #00abff;">${song.url}</div>
                <button id="closeInfo" style="padding: 8px 20px; background: #ff0000; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">CLOSE</button>
            </div>
        `;
        infoBox.style.display = 'flex';
        document.getElementById('closeInfo').onclick = () => infoBox.style.display = 'none';
        setupDragging(document.getElementById('infoDragHandle'), infoBox, true);
    }

    // ---- MAIN MENU ----
    const menu = document.createElement('div');
    Object.assign(menu.style, {
        position: 'fixed', top: '15%', left: '35%',
        background: '#121212', border: '2px solid #ff0000', borderRadius: '12px',
        zIndex: '10000', display: 'none', flexDirection: 'column',
        width: '450px', maxHeight: '88vh',
        boxShadow: '0 10px 40px rgba(0,0,0,0.8)', fontFamily: 'Segoe UI, sans-serif',
        color: 'white', overflow: 'hidden'
    });

    // -- Drag Handle --
    const dragHandle = document.createElement('div');
    dragHandle.innerText = ":: Music Menu (Drag) ::";
    Object.assign(dragHandle.style, {
        background: '#ff0000', color: 'white', padding: '8px', cursor: 'grab',
        textAlign: 'center', fontWeight: 'bold', fontSize: '11px', userSelect: 'none'
    });
    menu.appendChild(dragHandle);

    // -- Nav Tabs --
    const nav = document.createElement('div');
    nav.style.display = 'flex';
    nav.style.background = '#1a1a1a';

    const libBtn = document.createElement('button');
    const favBtn = document.createElement('button');
    const vidBtn = document.createElement('button');

    [libBtn, favBtn, vidBtn].forEach(b => {
        Object.assign(b.style, {
            flex: '1', padding: '10px', cursor: 'pointer', border: 'none',
            background: 'none', color: 'white', fontWeight: 'bold', fontSize: '12px'
        });
    });

    libBtn.innerText = "📁 Movies/songs"; favBtn.innerText = "⭐ Music"; vidBtn.innerText = "⚪ Movies";

    libBtn.onclick = () => { currentTab = 'library'; searchQuery = ''; updateTabStyles(); renderPlaylist(); };
    favBtn.onclick = () => { currentTab = 'favorites'; searchQuery = ''; updateTabStyles(); renderPlaylist(); };
    vidBtn.onclick = () => { currentTab = 'videos'; searchQuery = ''; updateTabStyles(); renderPlaylist(); };

    function updateTabStyles() {
        libBtn.style.borderBottom = currentTab === 'library' ? '3px solid #ff0000' : 'none';
        favBtn.style.borderBottom = currentTab === 'favorites' ? '3px solid #ffcc00' : 'none';
        vidBtn.style.borderBottom = currentTab === 'videos' ? '3px solid #00abff' : 'none';
    }

    nav.append(libBtn, favBtn, vidBtn);
    menu.appendChild(nav);

    // -- SEARCH BAR --
    const searchBar = document.createElement('input');
    searchBar.placeholder = "🔍 Search by name or keyword...";
    Object.assign(searchBar.style, {
        margin: '10px 15px 0 15px',
        padding: '9px 12px',
        background: '#1a1a1a',
        color: 'white',
        border: '1px solid #444',
        borderRadius: '6px',
        fontFamily: 'Segoe UI, sans-serif',
        fontSize: '13px',
        outline: 'none'
    });
    searchBar.addEventListener('input', () => {
        searchQuery = searchBar.value.toLowerCase().trim();
        renderPlaylist();
    });
    searchBar.addEventListener('keydown', (e) => e.stopPropagation());
    menu.appendChild(searchBar);

    // -- SCROLLABLE LIST --
    const listContainer = document.createElement('div');
    Object.assign(listContainer.style, {
        padding: '15px',
        overflowY: 'auto',
        flexGrow: '1',
        maxHeight: '350px',
        minHeight: '120px'
    });
    menu.appendChild(listContainer);

    function renderPlaylist() {
        listContainer.innerHTML = '';

        let filtered = savedSongs.filter(s => {
            if (currentTab === 'favorites') return s.fav;
            if (currentTab === 'videos') return s.vid;
            return !s.fav && !s.vid;
        });

        if (searchQuery) {
            const q = searchQuery.toLowerCase();
            filtered = filtered.filter(s =>
                s.name.toLowerCase().includes(q) ||
                s.url.toLowerCase().includes(q)
            );
        }

        const count = document.createElement('div');
        count.style = 'font-size: 11px; color: #666; margin-bottom: 8px;';
        count.textContent = `${filtered.length} item${filtered.length !== 1 ? 's' : ''}`;
        listContainer.appendChild(count);

        if (filtered.length === 0) {
            const empty = document.createElement('div');
            empty.style = 'color: #555; text-align: center; padding: 30px 0; font-size: 13px;';
            empty.textContent = searchQuery ? 'No matches found.' : 'This folder is empty.';
            listContainer.appendChild(empty);
            return;
        }

        filtered.forEach(song => {
            const row = document.createElement('div');
            row.style = 'display: flex; gap: 8px; margin-bottom: 8px; align-items: center;';

            const star = document.createElement('button');
            star.innerText = song.fav ? "★" : "☆";
            star.style = `background:none; border:none; color:${song.fav ? '#ffcc00':'#444'}; cursor:pointer; font-size:18px;`;
            star.onclick = () => { song.fav = !song.fav; if(song.fav) song.vid = false; save(); };

            const circ = document.createElement('button');
            circ.innerText = song.vid ? "●" : "○";
            circ.style = `background:none; border:none; color:${song.vid ? '#00abff':'#444'}; cursor:pointer; font-size:18px;`;
            circ.onclick = () => { song.vid = !song.vid; if(song.vid) song.fav = false; save(); };

            const mainBtn = document.createElement('button');
            mainBtn.innerText = song.name;
            mainBtn.style = 'flex-grow:1; padding:8px; background:#222; color:white; border:1px solid #333; border-radius:4px; text-align:left; font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; cursor:pointer;';
            mainBtn.onclick = () => window.open(song.url, '_blank');

            const infoBtn = document.createElement('button');
            infoBtn.innerText = "⋮";
            infoBtn.style = 'background:none; border:none; color:#888; cursor:pointer; font-size:18px; padding:0 5px;';
            infoBtn.onclick = (e) => { e.stopPropagation(); showInfo(song); };

            const del = document.createElement('button');
            del.innerText = "✕";
            del.style = 'background:none; border:none; color:#444; cursor:pointer; font-size:14px;';
            del.onclick = () => { savedSongs = savedSongs.filter(s => s !== song); save(); };

            row.append(star, circ, mainBtn, infoBtn, del);
            listContainer.appendChild(row);
        });
    }

    // -- FOOTER --
    const footer = document.createElement('div');
    footer.style = 'padding: 15px; border-top: 1px solid #333; display: flex; flex-direction: column; gap: 8px;';

    const iName = document.createElement('input');
    iName.placeholder = "Name...";
    iName.style = 'padding:8px; background:#1a1a1a; color:white; border:1px solid #444; border-radius:4px;';

    const iUrl = document.createElement('input');
    iUrl.placeholder = "URL...";
    iUrl.style = 'padding:8px; background:#1a1a1a; color:white; border:1px solid #444; border-radius:4px;';

    const addBtn = document.createElement('button');
    addBtn.innerText = "Add to Library";
    addBtn.style = 'padding:10px; background:#ff0000; color:white; border:none; border-radius:5px; cursor:pointer; font-weight:bold;';

    addBtn.onclick = () => {
        if(iName.value && iUrl.value) {
            savedSongs.push({ name: iName.value, url: iUrl.value, fav: false, vid: false });
            iName.value = '';
            iUrl.value = '';
            save();
        }
    };

    footer.append(iName, iUrl, addBtn);
    menu.appendChild(footer);

    // ---- UNIVERSAL SAVE ----
    function save() {
        GM_setValue('tm_universal_playlist', savedSongs);
        renderPlaylist();
    }

    // ---- DRAG LOGIC ----
    function setupDragging(handle, target, isCentered = false) {
        let dragging = false, ox, oy;
        handle.onmousedown = (e) => {
            dragging = true;
            if (isCentered) {
                const rect = target.getBoundingClientRect();
                target.style.transform = 'none';
                target.style.left = rect.left + 'px';
                target.style.top = rect.top + 'px';
            }
            ox = e.clientX - target.offsetLeft;
            oy = e.clientY - target.offsetTop;
        };
        document.addEventListener('mousemove', (e) => {
            if (dragging) {
                target.style.left = (e.clientX - ox) + 'px';
                target.style.top = (e.clientY - oy) + 'px';
            }
        });
        document.addEventListener('mouseup', () => dragging = false);
    }

    setupDragging(dragHandle, menu);

    // ---- KEYBOARD TOGGLE ----
    window.addEventListener('keydown', (e) => {
        if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
        if (e.key === '/') {
            e.preventDefault();
            const isHidden = menu.style.display === 'none';
            menu.style.display = isHidden ? 'flex' : 'none';
            if (!isHidden) infoBox.style.display = 'none';
            else searchBar.focus();
        }
    });

    // ---- FINAL APPEND & INIT ----
    document.body.appendChild(menu);
    updateTabStyles();
    renderPlaylist();

})();