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或关注我们的公众号极客氢云获取最新地址