您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a Download button to every BitChute video page
当前为
// ==UserScript== // @name BitChute | Video Download Button // @namespace de.sidneys.userscripts // @homepage https://gist.githubusercontent.com/sidneys/b4783b0450e07e12942aa22b3a11bc00/raw/ // @version 5.1.0 // @description Adds a Download button to every BitChute video page // @author sidneys // @icon https://www.bitchute.com/static/images/android-icon-192x192.png // @include *://*bitchute.com/video/* // @require https://gf.qytechs.cn/scripts/38888-greasemonkey-color-log/code/Greasemonkey%20%7C%20Color%20Log.js // @require https://gf.qytechs.cn/scripts/38889-greasemonkey-waitforkeyelements-2018/code/Greasemonkey%20%7C%20waitForKeyElements%202018.js // @run-at document-end // @grant GM_addStyle // @grant GM.xmlHttpRequest // @grant unsafeWindow // @connect bitchute.com // ==/UserScript== /** * ESLint configuration */ /* global DEBUG, waitForKeyElements */ /** * @default * @constant */ DEBUG = false /** * @callback saveAsCallback * @param {Error} error - Error * @param {Number} progress - Progress fraction * @param {Boolean} complete - Completion Yes/No */ /** * Download any file via XHR * @param {String} url - Target URL * @param {String=} name - Filename * @param {saveAsCallback} callback - Callback */ let saveAs = (url, name, callback = () => {}) => { console.debug('saveAs') // Parse URL const urlObject = new URL(url) const urlHref = urlObject.href const urlFilename = urlObject.pathname.replace(/\?.*$/, '') // Resolve filename const filename = name || urlFilename // Create Download Request GM.xmlHttpRequest({ method: 'GET', url: urlHref, responseType: 'blob', headers: { 'Accept': 'application/octet-stream', 'User-Agent': 'Mozilla/5.0' }, onerror: (response) => { console.debug('saveAs', 'onerror') callback(new Error('Network Request failed.')) }, onprogress: (response) => { //console.debug('saveAs', 'onprogress') callback(null, (response.loaded / response.total)) }, onload: (response) => { console.debug('saveAs', 'onload', 'response.readyState:', response.readyState) // Create file object with binary MIME type (application/octet-stream) const file = new File([response.response], '', { type: 'application/octet-stream' }) // Create ObjectURL const objectUrl = window.URL.createObjectURL(file) // Create helper element const anchorElement = document.createElement('a') anchorElement.style.display = 'none' anchorElement.target = '_self' anchorElement.href = objectUrl anchorElement.download = filename document.body.appendChild(anchorElement) // Trigger download anchorElement.click() // Revoke ObjectURL window.URL.revokeObjectURL(objectUrl) // Remove element anchorElement.remove() callback(null, 1, true) } }) } /** * File extension * @default * @constant */ const defaultExtension = 'mp4' /** * Inject Stylesheet */ let injectStylesheet = () => { console.debug('injectStylesheet') GM_addStyle(` /* ========================================================================== ELEMENTS ========================================================================== */ /* #download-button ========================================================================== */ #download-button, #download-button:hover { color: rgb(88, 87, 89); display: inline-block; animation: fade-in 0.3s; pointer-events: all; filter: none; cursor: pointer; white-space: nowrap; transition: all 500ms ease-in-out; opacity: 0; } #download-button.ready { opacity: 1; animation: swing-in-bottom-fwd 1500ms cubic-bezier(0.175, 0.885, 0.320, 1.275) both; } #download-button.busy { pointer-events: none; cursor: default; animation: pulsating-opacity 2000ms cubic-bezier(0.455, 0.03, 0.515, 0.955) 0s infinite alternate; } #download-button-label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } #download-button svg path { fill: rgb(88, 87, 89); } /* .video-statistics ========================================================================== */ .video-information .video-statistics { white-space: nowrap; } /* ========================================================================== ANIMATIONS ========================================================================== */ @keyframes pulsating-opacity { 0% { filter: opacity(0.75); } 25% { filter: opacity(0.75); } 50% { filter: opacity(0.25); } 75% { filter: opacity(0.75); } 100% { filter: opacity(0.75); } } @keyframes swing-in-bottom-fwd { 0% { transform: rotateX(100deg); transform-origin: bottom; filter: opacity(0); } 100% { transform: rotateX(0); transform-origin: bottom; filter: opacity(1); } } `) } /** * Generate Filename * @return {String} Filename */ let generateFilename = () => { console.debug('generateFilename') const videoAuthor = document.querySelector('p.video-author > a').textContent.trim() const pageTitle = document.querySelector('h1.page-title').textContent.trim() return `${videoAuthor} - ${pageTitle}.${defaultExtension}` } /** * Render download button * @param {String} url - Target URL */ let renderDownloadButton = (url) => { console.debug('renderDownloadButton') // Parse URL const urlObject = new URL(url) const urlHref = urlObject.href // Parent const parentElement = document.querySelector('.video-information .video-statistics') // Button const buttonElement = document.createElement('a') buttonElement.id = 'download-button' buttonElement.innerHTML = ` <i class="action-icon far fa-cloud-download-alt fa-fw"></i> <span id="download-button-label">Download</span> ` parentElement.appendChild(buttonElement) buttonElement.classList.add('ready') // Label const labelElement = document.querySelector('#download-button-label') buttonElement.onclick = () => { saveAs(urlHref, generateFilename(), (error, progress, complete) => { if (error) { labelElement.innerText = `${error}` return } if (progress) { const percentage = (progress * 100).toFixed(2) labelElement.innerText = `${percentage}%` buttonElement.classList.add('busy') } if (complete) { labelElement.innerText = `Complete.` buttonElement.classList.remove('busy') } }) } /** * Filename * @type {String} */ console.info('Added download button:', urlHref) } /** * Init */ let init = () => { console.debug('init') // Add Stylesheet injectStylesheet() if (!unsafeWindow.client) { // Firefox waitForKeyElements('source', (element) => { renderDownloadButton(element.src) }) } else { // Chromium waitForKeyElements('video', () => { renderDownloadButton(unsafeWindow.client.torrents[0].urlList[0]) }) } } /** * @listens window:Event#load */ window.addEventListener('load', () => { console.debug('window#load') init() })
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址