Skip to content

Instantly share code, notes, and snippets.

@sidneys
Last active July 9, 2023 19:09
Show Gist options
  • Save sidneys/1731ea8ca69af49ca6e138d045c09971 to your computer and use it in GitHub Desktop.
Save sidneys/1731ea8ca69af49ca6e138d045c09971 to your computer and use it in GitHub Desktop.
Userscript | Download File As
// ==UserScript==
// @name Save-As Userscript Library
// @namespace de.sidneys.userscripts
// @homepage https://gist.githubusercontent.com/sidneys/1731ea8ca69af49ca6e138d045c09971/raw/
// @version 3.0.0
// @description "Save As..." for any URL
// @author sidneys
// @icon https://www.greasespot.net/favicon.ico
// @include *://*/*
// @grant unsafeWindow
// ==/UserScript==
/**
* @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 XHR Request
const request = new XMLHttpRequest()
// XHR Open
request.open('GET', urlHref)
// XHR MIME Type
request.responseType = 'blob'
request.overrideMimeType('application/octet-stream')
// XHR Headers
request.setRequestHeader('Accept-Ranges', 'bytes')
request.setRequestHeader('Content-Disposition', 'attachment')
request.setRequestHeader('Content-Transfer-Encoding', 'binary')
request.setRequestHeader('Content-Type', 'application/octet-stream')
// Error handler
request.onerror = () => {
console.debug('saveAs', 'onerror')
callback(new Error('Network Request failed.'))
}
// Progress handler
request.onprogress = (event) => {
//console.debug('saveAs', 'onprogress')
callback(null, (event.loaded / event.total))
}
// Completion handler
request.onload = (event) => {
console.debug('saveAs', 'onload', event)
console.info('onload', 'xhr.readyState', request.readyState) // readyState will be 1
// Create new File object with generic MIME type
const file = new File([request.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)
}
// XHR Send
request.send()
}
unsafeWindow.saveAs = saveAs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment