您需要先安装一个扩展,例如 篡改猴、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 9.9.0 // @description Adds a Download button to every BitChute video page // @author sidneys // @icon https://www.bitchute.com/static/v76/images/android-icon-192x192.png // @include *://www.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/374849-library-onelementready-es6/code/Library%20%7C%20onElementReady%20ES6.js // @run-at document-end // @grant GM.addStyle // @grant GM.download // @grant unsafeWindow // @connect bitchute.com // ==/UserScript== /** * ESLint * @global */ /* global DEBUG */ /* global onElementReady */ /** * @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 // Trigger Download Request GM.download({ name: filename, onerror: (download) => { console.debug('saveAs', 'onerror') callback(new Error(download.error ? download.error.toUpperCase() : 'Unknown')) }, onload: (download) => { console.debug('saveAs', 'onload') console.dir(download) callback(null) }, ontimeout: (download) => { console.debug('saveAs', 'ontimeout') console.dir(download) callback(new Error('Network timeout')) }, saveAs: true, url: urlHref }) } /** * 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(41, 113, 237); 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 2000ms cubic-bezier(0.175, 0.885, 0.320, 1.275) both, flash 500ms ease-in-out; animation-delay: 0s, 2000ms; } #download-button.error { color: rgb(255, 93, 12); } #download-button.busy { pointer-events: none; cursor: default; animation: pulsating-opacity 1000ms cubic-bezier(0.455, 0.03, 0.515, 0.955) 0s infinite alternate; } #download-button-label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; transition: all 500ms ease-in-out; } /* .video-statistics ========================================================================== */ .video-information .video-statistics { white-space: nowrap; } /* ========================================================================== ANIMATIONS ========================================================================== */ @keyframes pulsating-opacity { 0% { filter: opacity(0.95); } 25% { filter: opacity(0.95); } 50% { filter: opacity(0.50); } 75% { filter: opacity(0.95); } 100% { filter: opacity(0.95); } } @keyframes flash { 0% { color: rgb(41, 113, 237); } 50% { color: white; } 100% { color: rgb(41, 113, 237); } } @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') // Label Text const labelText = 'Download' // Parse URL const urlObject = new URL(url) const urlHref = urlObject.href const urlFilename = urlObject.pathname.replace(/\?.*$/, '') // Parent const parentElement = document.querySelector('.video-information .video-statistics') // Button Element const buttonElement = document.createElement('a') buttonElement.innerHTML = ` <i class="action-icon far fa-cloud-download-alt fa-fw"></i> <span id="download-button-label">${labelText}</span> ` buttonElement.id = 'download-button' buttonElement.href = urlHref buttonElement.download = urlFilename buttonElement.target = '_blank' buttonElement.rel = 'noopener noreferrer' buttonElement.type = 'video/mp4' parentElement.appendChild(buttonElement) buttonElement.classList.add('ready') // Label Element const labelElement = document.querySelector('#download-button-label') // Button Events buttonElement.onclick = (event) => { // Cancel regular download event.preventDefault() // Reset label, style labelElement.innerText = labelText buttonElement.classList.remove('error') buttonElement.classList.add('busy') // Start download saveAs(urlHref, generateFilename(), (error) => { // Error if (error) { labelElement.innerText = `${error}. Retry?` buttonElement.classList.remove('busy') buttonElement.classList.add('error') return } // Success 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 onElementReady('source', false, (element) => { renderDownloadButton(element.src) }) } else { // Chromium onElementReady('video', false, (element) => { renderDownloadButton(unsafeWindow.client.torrents[0].urlList[0]) }) } } /** * @listens window:Event#load */ window.addEventListener('load', () => { console.debug('window#load') init() })
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址