您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一键复制 Apple Music 当前页面中的专辑链接到剪贴板;兼容 Shadow DOM。
// ==UserScript== // @name Apple Music 专辑链接一键复制 // @namespace https://example.com/userscripts // @version 1.0.1 // @description 一键复制 Apple Music 当前页面中的专辑链接到剪贴板;兼容 Shadow DOM。 // @author onlys // @license MIT // @match https://music.apple.com/* // @match https://beta.music.apple.com/* // @run-at document-idle // @grant GM_setClipboard // @grant GM_registerMenuCommand // @grant GM_notification // @grant GM_addStyle // ==/UserScript== (function () { 'use strict'; // ============ Utils ============ function notify(message) { try { if (typeof GM_notification === 'function') { GM_notification({ text: message, title: 'Apple Music 链接助手', timeout: 3000 }); } else { console.log('[Apple Music 链接助手]', message); alert(message); } } catch { console.log('[Apple Music 链接助手]', message); } } function getAllAnchorsDeep(root = document) { const result = new Set(); const stack = [root]; const seen = new WeakSet(); // 使用 WeakSet 避免内存泄漏 while (stack.length) { const node = stack.pop(); if (!node || seen.has(node)) continue; seen.add(node); try { if (node.querySelectorAll) { // 直接查找专辑链接,提高效率 const albumLinks = node.querySelectorAll('a[href*="/album/"]'); albumLinks.forEach(a => result.add(a)); // 检查 Shadow DOM const shadowHosts = node.querySelectorAll('*'); for (const el of shadowHosts) { if (el.shadowRoot && !seen.has(el.shadowRoot)) { stack.push(el.shadowRoot); } } } // 处理 Shadow Root if (node instanceof ShadowRoot) { const albumLinks = node.querySelectorAll('a[href*="/album/"]'); albumLinks.forEach(a => result.add(a)); const shadowHosts = node.querySelectorAll('*'); for (const el of shadowHosts) { if (el.shadowRoot && !seen.has(el.shadowRoot)) { stack.push(el.shadowRoot); } } } } catch (e) { console.warn('[Apple Music 链接助手] 遍历节点时出错:', e); continue; } } return Array.from(result); } function extractAlbumIdFromUrl(urlString) { if (!urlString || typeof urlString !== 'string') return null; try { const u = new URL(urlString); const parts = u.pathname.split('/').filter(Boolean); const albumIdx = parts.indexOf('album'); if (albumIdx >= 0 && parts.length > albumIdx + 1) { // 从后往前查找数字 ID for (let i = parts.length - 1; i > albumIdx; i--) { const seg = parts[i]; if (/^\d+$/.test(seg)) return seg; } } } catch (e) { console.warn('[Apple Music 链接助手] 提取专辑 ID 失败:', urlString, e); } return null; } function isAlbumLink(urlString) { if (!urlString || typeof urlString !== 'string') return false; try { const u = new URL(urlString); // 更精确的域名检查 if (!/^(.*\.)?apple\.com$/.test(u.hostname)) return false; const parts = u.pathname.split('/').filter(Boolean); const albumIdx = parts.indexOf('album'); if (albumIdx < 0) return false; // 确保在 /album/ 之后有数字 ID return parts.slice(albumIdx + 1).some(seg => /^\d+$/.test(seg)); } catch (e) { console.warn('[Apple Music 链接助手] URL 解析失败:', urlString, e); return false; } } function getAlbumLinksFromPage() { try { const anchors = getAllAnchorsDeep(document); const idToHref = new Map(); for (const a of anchors) { if (!a || !a.href) continue; const href = a.href; if (!isAlbumLink(href)) continue; const id = extractAlbumIdFromUrl(href); if (!id) continue; // 按专辑 ID 去重,保留第一个遇到的(通常是最短的) if (!idToHref.has(id)) { // 清理 URL 参数 try { const cleanUrl = new URL(href); cleanUrl.search = ''; // 移除查询参数 idToHref.set(id, cleanUrl.toString()); } catch { idToHref.set(id, href); } } } const result = Array.from(idToHref.values()); console.log(`[Apple Music 链接助手] 检测到 ${result.length} 个专辑链接`); return result; } catch (e) { console.error('[Apple Music 链接助手] 获取链接失败:', e); return []; } } async function copyToClipboard(links) { const text = links.join(' '); if (!text) { notify('未找到任何专辑链接,请确认页面内容已加载。'); return false; } // Try GM_setClipboard first try { if (typeof GM_setClipboard === 'function') { GM_setClipboard(text, { type: 'text', mimetype: 'text/plain' }); notify(`已复制 ${links.length} 条专辑链接到剪贴板`); return true; } } catch (e) { console.warn('GM_setClipboard failed:', e); } // Fallback: try native clipboard API try { await navigator.clipboard.writeText(text); notify(`已复制 ${links.length} 条专辑链接到剪贴板`); return true; } catch (e) { console.warn('navigator.clipboard failed:', e); } // Final fallback: show text in prompt for manual copy try { const shortText = text.length > 200 ? text.substring(0, 200) + '...' : text; prompt('复制失败,请手动复制以下内容:', text); notify('请手动复制弹窗中的内容'); return true; } catch (e) { notify('复制失败,请检查浏览器权限设置'); return false; } } function sleep(ms) { return new Promise(res => setTimeout(res, ms)); } // 检测是否为专辑/音乐相关页面 function isRelevantPage() { try { const path = window.location.pathname.toLowerCase(); const relevantPaths = ['/room/', '/album/', '/playlist/', '/artist/', '/station/', '/browse/']; // 检查 URL 路径 if (relevantPaths.some(p => path.includes(p))) return true; // 检查页面内容 const indicators = [ '[data-testid*="album"]', '[data-testid*="playlist"]', '.album', '.playlist', 'a[href*="/album/"]' ]; return indicators.some(selector => { try { return document.querySelector(selector) !== null; } catch { return false; } }); } catch (e) { console.warn('[Apple Music 链接助手] 页面检测失败:', e); return true; // 错误时默认显示 } } // 智能等待页面内容加载 async function waitForContent(timeout = 5000) { const start = Date.now(); let attempts = 0; const maxAttempts = Math.floor(timeout / 200); while (Date.now() - start < timeout && attempts < maxAttempts) { attempts++; try { const links = getAlbumLinksFromPage(); if (links.length > 0) { console.log(`[Apple Music 链接助手] 内容加载完成,共 ${attempts} 次尝试`); return true; } // 检查加载指示器 const loadingSelectors = [ '[data-testid*="loading"]', '.loading', '.spinner', '[role="progressbar"]', '.progress' ]; const hasLoading = loadingSelectors.some(selector => { try { return document.querySelector(selector) !== null; } catch { return false; } }); if (!hasLoading && attempts > 5) { // 没有加载指示器且已经尝试多次,可能已经加载完成 await sleep(500); break; } } catch (e) { console.warn('[Apple Music 链接助手] 等待内容时出错:', e); } await sleep(200); } console.log(`[Apple Music 链接助手] 等待结束,共尝试 ${attempts} 次`); return false; } // ============ Actions ============ async function actionCopyCurrent() { // 先等待内容加载 await waitForContent(); const links = getAlbumLinksFromPage(); const success = await copyToClipboard(links); if (success) { console.log('[Apple Music 链接助手] 采集到专辑链接:', links); } } // ============ UI ============ function addMenuCommands() { if (typeof GM_registerMenuCommand === 'function') { GM_registerMenuCommand('复制专辑链接', actionCopyCurrent); } } function addFloatingButton() { const btnId = '__am_links_helper_btn__'; if (document.getElementById(btnId)) return; GM_addStyle(` #${btnId} { position: fixed; right: 16px; bottom: 16px; z-index: 999999; font-family: system-ui, -apple-system, Segoe UI, Roboto, PingFang SC, Helvetica, Arial, sans-serif; } #${btnId} .am-btn { background: #111827; color: #fff; border: 1px solid #374151; border-radius: 8px; padding: 8px 10px; cursor: pointer; font-size: 12px; box-shadow: 0 4px 12px rgba(0,0,0,.15); transition: all 0.2s ease; } #${btnId} .am-btn:hover { background: #1f2937; transform: translateY(-1px); } #${btnId} .am-panel { position: absolute; bottom: 100%; right: 0; margin-bottom: 8px; background: #111827; color: #e5e7eb; border: 1px solid #374151; border-radius: 8px; padding: 8px; display: none; min-width: 220px; box-shadow: 0 8px 25px rgba(0,0,0,.3); } #${btnId} .am-panel.show { display: block; } #${btnId} .am-panel button { width: 100%; margin: 4px 0; background: #1f2937; color: #e5e7eb; border: 1px solid #374151; border-radius: 6px; padding: 6px 8px; cursor: pointer; font-size: 12px; transition: background 0.2s ease; } #${btnId} .am-panel button:hover { background: #374151; } #${btnId} .am-panel button:disabled { background: #0f172a; color: #6b7280; cursor: not-allowed; } #${btnId} .am-small { font-size: 11px; color: #9ca3af; margin-top: 6px; } #${btnId} .am-count { display: inline-block; background: #dc2626; color: white; border-radius: 10px; padding: 2px 6px; font-size: 10px; margin-left: 4px; min-width: 16px; text-align: center; } `); const container = document.createElement('div'); container.id = btnId; container.innerHTML = ` <button class="am-btn" type="button">专辑链接<span class="am-count" style="display:none;">0</span></button> <div class="am-panel"> <button type="button" data-action="copy">复制专辑链接</button> <div class="am-small">链接以空格分隔,方便批量使用</div> </div> `; document.documentElement.appendChild(container); const toggleBtn = container.querySelector('.am-btn'); const panel = container.querySelector('.am-panel'); const countBadge = container.querySelector('.am-count'); // 简化的面板切换逻辑 toggleBtn.addEventListener('click', () => { const isVisible = panel.classList.contains('show'); panel.classList.toggle('show', !isVisible); // 更新计数显示 if (!isVisible) { updateLinkCount(); } }); // 点击外部关闭面板 document.addEventListener('click', (e) => { if (!container.contains(e.target)) { panel.classList.remove('show'); } }); // 更新链接计数的函数 function updateLinkCount() { try { const links = getAlbumLinksFromPage(); const count = links.length; if (countBadge) { countBadge.textContent = count; countBadge.style.display = count > 0 ? 'inline-block' : 'none'; } // 禁用按钮如果没有链接 const buttons = panel.querySelectorAll('button[data-action]'); buttons.forEach(btn => { if (btn) { btn.disabled = count === 0; btn.title = count === 0 ? '当前页面没有检测到专辑链接' : `共检测到 ${count} 个专辑链接`; } }); return count; } catch (e) { console.error('[Apple Music 链接助手] 更新计数失败:', e); return 0; } } panel.addEventListener('click', async (e) => { const btn = e.target.closest('button[data-action]'); if (!btn || btn.disabled) return; // 关闭面板 panel.classList.remove('show'); const action = btn.getAttribute('data-action'); if (action === 'copy') { await actionCopyCurrent(); } }); // 初始更新计数 setTimeout(updateLinkCount, 1000); } // 防抖函数 function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function init() { try { if (!isRelevantPage()) { console.log('[Apple Music 链接助手] 当前页面非音乐相关页面,跳过初始化'); return false; } addMenuCommands(); addFloatingButton(); console.log('[Apple Music 链接助手] 初始化完成'); return true; } catch (e) { console.error('[Apple Music 链接助手] 初始化失败:', e); return false; } } // 页面为 SPA,路由切换后重新初始化,使用防抖优化性能 const debouncedInit = debounce(() => { try { if (isRelevantPage()) { addFloatingButton(); } } catch (e) { console.error('[Apple Music 链接助手] 防抖初始化失败:', e); } }, 500); // 监听路由变化(针对 SPA) let currentUrl = window.location.href; const checkUrlChange = () => { try { const newUrl = window.location.href; if (newUrl !== currentUrl) { console.log('[Apple Music 链接助手] 路由变化:', currentUrl, '->', newUrl); currentUrl = newUrl; debouncedInit(); } } catch (e) { console.error('[Apple Music 链接助手] 检查 URL 变化失败:', e); } }; // 使用更精确的观察器 let mutationObserver = null; try { mutationObserver = new MutationObserver((mutations) => { try { // 只在有实际 DOM 变化时触发 const hasSignificantChange = mutations.some(mutation => mutation.type === 'childList' && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0) ); if (hasSignificantChange) { checkUrlChange(); } } catch (e) { console.error('[Apple Music 链接助手] MutationObserver 处理失败:', e); } }); // 只监听主要内容区域的变化 const observeTarget = document.querySelector('#app') || document.querySelector('main') || document.querySelector('[role="main"]') || document.body; if (observeTarget) { mutationObserver.observe(observeTarget, { childList: true, subtree: true, attributes: false, // 不监听属性变化 characterData: false // 不监听文本变化 }); console.log('[Apple Music 链接助手] MutationObserver 初始化成功'); } else { console.warn('[Apple Music 链接助手] 未找到合适的监听目标'); } } catch (e) { console.error('[Apple Music 链接助手] MutationObserver 初始化失败:', e); } // 监听 pushState/popState 事件(SPA 路由变化) try { const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function(...args) { try { originalPushState.apply(history, args); debouncedInit(); } catch (e) { console.error('[Apple Music 链接助手] pushState 处理失败:', e); originalPushState.apply(history, args); } }; history.replaceState = function(...args) { try { originalReplaceState.apply(history, args); debouncedInit(); } catch (e) { console.error('[Apple Music 链接助手] replaceState 处理失败:', e); originalReplaceState.apply(history, args); } }; window.addEventListener('popstate', debouncedInit); console.log('[Apple Music 链接助手] 路由监听初始化成功'); } catch (e) { console.error('[Apple Music 链接助手] 路由监听初始化失败:', e); } // 初始化 console.log('[Apple Music 链接助手] 开始初始化...'); init(); // 清理函数(在页面卸载时调用) window.addEventListener('beforeunload', () => { try { if (mutationObserver) { mutationObserver.disconnect(); } console.log('[Apple Music 链接助手] 清理完成'); } catch (e) { console.error('[Apple Music 链接助手] 清理失败:', e); } }); })();