Music Manager - Universal Sync

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

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greasyfork.org/scripts/579897/1835463/Music%20Manager%20-%20Universal%20Sync.js

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==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();

})();