// ==UserScript== // @name 获取哔哩哔哩视频音频 // @description 下载音频 // @license The Unlicense // @version 1.0.0 // @author girl-dream // @namespace https://github.com/girl-dream/ // @match https://www.bilibili.com/video/* // @icon https://www.bilibili.com/favicon.ico // @run-at document-end // ==/UserScript== (async () => { const data = __playinfo__.data.dash if (!data) { throw new Error("未获取音频信息") } const title = document.querySelector('.video-title').textContent.trim().replaceAll(/[\\/:*?"<>|]/g, '_') const isElementLoaded = async (selector, root = document) => { const getElement = () => root.querySelector(selector) return new Promise((resolve) => { const element = getElement() if (element) return resolve(element) const observer = new MutationObserver(() => { const element2 = getElement() if (!element2) return resolve(element2) observer.disconnect() }); observer.observe(root === document ? root.documentElement : root, { childList: true, subtree: true }) }) } const pop = document.createElement('div') pop.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 999999;` const content = document.createElement('div') content.style.cssText = ` background: var(--bg1_float); padding: 20px; border-radius: 8px; width: 200px; max-width: 90%; max-height: 80vh; overflow-y: auto; color:var(--text1);` content.innerHTML = `
选择音频类型
` content.querySelector('#cancel-btn').onclick = () => { pop.remove() } const select = content.querySelector('select') content.querySelector('#confirm-btn').onclick = () => { const url = select.options[select.selectedIndex].value download(url) pop.remove() } if (Array.isArray(data.audio)) { data.audio.forEach(e => { const op = document.createElement('option') op.value = e.id switch (e.id) { case 30232: op.textContent = 132 + 'K' break case 30280: op.textContent = 192 + 'K' break case 30216: op.textContent = 64 + 'K' break } op.value = e.backupUrl.find(e => (new URL(e)).port === '') select.append(op) }) } if (Array.isArray(data.dolby?.audio)) { const op = document.createElement('option') op.value = data.dolby.audio[0].backupUrl.find(e => (new URL(e)).port === '') op.textContent = '杜比音效' select.append(op) } if (data.flac != null) { const op = document.createElement('option') op.value = data.flac.audio.backupUrl.find(e => (new URL(e)).port === '') op.textContent = 'Hi-Res' select.append(op) } pop.append(content) const toolbarLeft = document.querySelector('.video-toolbar-left-main') const progressDiv = document.createElement('div') progressDiv.style.color = 'var(--text2)' const div = document.createElement('div') div.className = 'video-toolbar-right-item' div.title = '下载音频' div.innerHTML = '' div.style.cssText = ` width: 35px; height: 35px; margin-right: 18px;` div.addEventListener('click', () => { document.body.append(pop) }) await Promise.all([isElementLoaded('.nav-search-input[maxlength]'), isElementLoaded('.view-icon[width]')]) toolbarLeft.append(div, progressDiv) function download(audioUrl) { const xhr = new XMLHttpRequest() xhr.open('GET', audioUrl, true) xhr.responseType = 'blob' xhr.onload = function () { if (xhr.status === 200) { const blob = new Blob([xhr.response]) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = title + '.m4s' a.click() URL.revokeObjectURL(url) progressDiv.textContent = null } else { console.error('下载失败,状态码:' + xhr.status) } } xhr.onerror = function (error) { console.error('请求失败:', error) } xhr.onprogress = function (progress) { progressDiv.textContent = `下载进度: ${(progress.loaded / progress.total * 100).toFixed(2)}%` } xhr.send() } })();