Press '/' for menu. Songs stay saved across ALL different websites! Now with search + hover URL scanner that auto-copies.
Tätä skriptiä ei tulisi asentaa suoraan. Se on kirjasto muita skriptejä varten sisällytettäväksi metadirektiivillä // @require https://update.greasyfork.org/scripts/579897/1835463/Music%20Manager%20-%20Universal%20Sync.js.
// ==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();
})();