- // ==UserScript==
- // @name NutAID Consolidated Script
- // @match *://*/*
- // @version 1.2.0-indev4
- // @author nutzlos
- // @description Nut's All Image Downloader.
- // @run-at document-start
- // @inject-into content
- // @sandbox DOM
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_xmlhttpRequest
- // @connect *
-
- // @namespace https://greasyfork.org/users/1455562
- // ==/UserScript==
-
-
- (function (){
- const LOGGING = "console"; //possible values: false or "", "log" or true, "console"
- let OPTIONS = {
- trackingProtection: GM_getValue('trackingProtection', true),
- arbitraryFillStyle: GM_getValue('arbitraryFillStyle', false),
- allowText: GM_getValue('allowText', false),
- mergedDownloads: GM_getValue('mergedDownloads', false),
- binbMerging: GM_getValue('binbMerging', true),
- modifyImgSrcLoading: GM_getValue('modifyImgSrcLoading', false),
- keys: {
- toContext: 'xyyxyxyyxxxy',
- toPageTop: 'asaasssassaas'
- }
- }
-
- let keySeed = GM_getValue('communicationKey', {
- lastUsed: -Infinity,
- value: 0
- })
- //generate new one if key has been unused for more than 2 hours
- if (new Date() - keySeed.lastUsed > 7.2e6) {
- keySeed.value = (Math.random() * 1e16) & (0xffffffff)
- }
- keySeed.lastUsed = (new Date()).valueOf()
- GM_setValue('communicationKey', keySeed)
-
- const generateKey = ((seed) => {
- let state = seed
- const xorshift = () => {
- state ^= state << 13
- state ^= state >> 17
- state ^= state << 5
- return state
- }
- let cipher = 'abcdefghijklmnopQRSTUVWxyzABCDEFGHIJKLMNOPqrstuvwXYZ'
- return (length, maxLength) => {
- if (maxLength && maxLength != length) {
- length = Math.abs(xorshift() % (maxLength - length)) + length
- }
- let key = ''
- for (let i = length; i > 0; --i) {
- key += cipher.charAt(Math.abs(xorshift() % cipher.length))
- }
- return key
- }
- })(keySeed.value)
-
- OPTIONS.keys.toContext = generateKey(30)
- OPTIONS.keys.toPageTop = generateKey(30)
-
-
- let pageScript = function (OPTIONS){
- let targetWindow = this
- //cross origin iframes will not be able to dispatch events to the top level window.
- //even the content script cannot work around that without being detectable.
- //therefore, we need to add nested menus
- let windowtop = targetWindow //since this is run in an iframe for added isolation, the target window will be the parent
- try {
- while (windowtop != window.top) {
- if ('dispatchEvent' in windowtop.parent) {
- windowtop = windowtop.parent
- } else {
- break
- }
- }
- } catch (e) {}
-
- const logger = (function () {
- return (title, that, args) => {
- if (title.includes('toString')) return;
- let e = new CustomEvent(OPTIONS.keys.toPageTop, {
- detail: {
- action: 'log',
- title: title,
- that: that,
- args: args,
- context: targetWindow
- }
- })
- windowtop.dispatchEvent(e)
- }
- })()
-
- if (targetWindow == windowtop) {
- function IndexTracker(){
- let values = []
- function getID(value) {
- if (!value) return null;
- let i = values.indexOf(value)
- if (i < 0) {
- values.push(value)
- i = values.indexOf(value)
- }
- return i
- }
- return getID
- }
- const capturedFramesIndex = new IndexTracker()
- capturedFramesIndex(this)
- targetWindow.addEventListener(OPTIONS.keys.toPageTop, (e) => {
- e.detail.context = capturedFramesIndex(e.detail.context)
- let ev = new CustomEvent(OPTIONS.keys.toContext, {
- detail: e.detail
- })
- dispatchEvent(ev)
- })
- }
-
- const canvasToBlob = HTMLCanvasElement.prototype.toBlob
- const ctxDrawImage = CanvasRenderingContext2D.prototype.drawImage
- const canvasToDataURL = HTMLCanvasElement.prototype.toDataURL
- const createUrlFromBlob = URL.createObjectURL
- const imgSetSrc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src').set
-
- let globalImageCounter = 0
- function captureNewImage(image = null, source = null, risky, scrambleParams) {
- //image = element the image was caught on, if applicable
- //source = the source object/element that was captured
- if (scrambleParams === undefined) {
- scrambleParams = dirtyFlag('get:params', image)
- }
- if (risky === undefined) {
- risky = dirtyFlag('get:risky', image) | dirtyFlag('get:risky', source)
- }
- let e = new CustomEvent(OPTIONS.keys.toPageTop, {
- detail: {
- action: 'captureImage',
- image: image,
- source: source,
- risky: risky,
- scrambleParams: scrambleParams,
- context: targetWindow
- }
- })
- windowtop.dispatchEvent(e)
- dirtyFlag('clear', image)
- }
-
- function extensionFromMimeType(mime) {
- let extension
- switch (mime) {
- case 'image/png':
- extension = '.png'
- break;
- case 'image/webp':
- extension = '.webp'
- break;
- case 'image/gif':
- extension = '.gif'
- break;
- case 'image/avif':
- extension = '.avif'
- break;
- case 'image/jxl':
- extension = '.jxl'
- break;
- case 'image/svg+xml':
- extension = '.svg'
- break;
- default:
- extension = '.jpeg'
- break
- }
- return extension
- }
- function GM_xmlhttpRequest_asyncWrapper (urlToFetch) {
- return new Promise((resolve, reject) => {
- let url = new URL(urlToFetch, location.href)
- let sameOrigin = urlToFetch.startsWith(location.origin)
- GM_xmlhttpRequest({
- url: url.href,
- responseType: 'blob',
- anonymous: false,
- headers: {
- 'Referer': location.origin + '/',
- 'Sec-Fetch-Dest': 'image',
- 'Sec-Fetch-Mode': 'no-cors',
- 'Sec-Fetch-Site': sameOrigin ? 'same-origin' : 'cross-site',
- 'Pragma': 'no-cache',
- 'Cache-Control': 'no-cache'
- },
- onload: resolve,
- onerror: reject
- })
- })
- }
- async function fetchImg(url) {
- if (url.startsWith('http')) {
- let r = await GM_xmlhttpRequest_asyncWrapper(url)
- let mime = r.responseHeaders.match(/content-type: (.*)/i)
- return {
- data: await r.response,
- contentType: mime && mime[1]
- }
- } else {
- let r = await fetch(url, {cache: 'force-cache'})
- return {
- data: await r.blob(),
- contentType: r.headers.get('content-type')
- }
- }
- }
-
- async function dlAllImgs() {
- let promises = []
- let filenameCounter = 0
- let fileCompletedCounter = 0
- dledImgsCounterElement.innerText = '(0 imgs)'
- async function getImg(x, i) {
- const url = x.getAttribute('data-original') || x.getAttribute('data-src') || x.getAttribute('content')
- let img
- try {
- img = await fetchImg(url)
- } catch (e) {
- img = await fetchImg(x.src)
- }
- let extension = extensionFromMimeType(img.contentType)
- let filename = String(i).padStart(4, '0')
- dledImgsCounterElement.innerText = `(${++fileCompletedCounter} imgs)`
- return {
- name: filename + extension,
- data: await img.data.arrayBuffer()
- }
- }
- for (let x of document.getElementsByTagName('img')) {
- promises.push(getImg(x, ++filenameCounter))
- }
- Promise.allSettled(promises).then((p) => {
- let files = []
- p.map(x => (x.status == 'fulfilled') && files.push(x.value))
- let zipData = SimpleZip.GenerateZipFrom(files)
- let blob = new Blob([zipData], {type: "octet/stream"})
- var url = createUrlFromBlob(blob);
- createDownload(url, (+new Date())+'.zip')
- dledImgsCounterElement.innerText = ''
- })
- }
-
- let dirtyFlagPerCanvas = new Map()
- /* Layout of the objects for the above: {
- * dirty: bool,
- * risky: bool,
- * timer: int,
- * lastSource: drawImage source, //used for scrambled images
- * drawImageSequence: [coords] //scrambled images
- }*/
- function dirtyFlag(op, canvas, img_source=null, drawImageParams) {
- if (op.startsWith('set')) {
- let o = {
- dirty: true,
- risky: false,
- timer: null,
- lastSource: null,
- drawImageSequence: []
- }
- if (dirtyFlagPerCanvas.has(canvas)) {
- o = dirtyFlagPerCanvas.get(canvas)
- o.dirty = true
- }
- if (img_source) {
- o.lastSource = img_source
- }
- if (op.includes('+timer')) {
- if (o.timer) {
- clearTimeout(o.timer)
- }
- o.timer = setTimeout((y,z)=>captureNewImage(y, z), 500, canvas, img_source)
- }
- if (op.includes('+risky')) {
- o.risky = true
- }
- if (op.includes('+params')) {
- if (drawImageParams) o.drawImageSequence.push(drawImageParams)
- }
- dirtyFlagPerCanvas.set(canvas, o)
- }
- if (op == 'clear') {
- if (dirtyFlagPerCanvas.has(canvas)) {
- o = dirtyFlagPerCanvas.get(canvas)
- if (o.timer) {
- clearTimeout(o.timer)
- }
- dirtyFlagPerCanvas.delete(canvas)
- }
- }
- if (op.startsWith('get')) {
- if (dirtyFlagPerCanvas.has(canvas)) {
- o = dirtyFlagPerCanvas.get(canvas)
- if (op == 'get:risky') {
- return o.risky
- }
- if (op == 'get:source') {
- return o.lastSource
- }
- if (op == 'get:params') {
- return o.drawImageSequence
- }
- return o.dirty
- } else {
- return false
- }
- }
- }
-
-
-
- const urlToBlobMapping = {}
-
-
- function setupIntercept(window){
- const funcsToConceil = new Map()
- const originalFuncs = new Map()
- const orig = (f) => originalFuncs.get(f)
-
- function createInterceptorFunction(originalFunction, newFunction, baseObj) {
- let originalProps = Object.getOwnPropertyDescriptors(originalFunction)
- let loggingTag = baseObj[Symbol.toStringTag]+'.'
- loggingTag += originalProps.name.value.includes(' ') ? `[${originalProps.name.value}]` : originalProps.name.value
- let interceptor = {
- fuckShit(){
- logger(loggingTag, this, arguments)
- return newFunction.apply(this, arguments)
- }
- }.fuckShit
- Object.defineProperties(interceptor, originalProps)
- funcsToConceil.set(interceptor, originalFunction)
- originalFuncs.set(newFunction, originalFunction)
- return interceptor
- }
-
- function interceptFunction(obj, prop, fun) {
- const old = obj[prop]
- let ifunc = createInterceptorFunction(old, fun, obj)
- obj[prop] = ifunc
- }
- function interceptProperty(obj, prop, getOrSet, fun) {
- const old = Object.getOwnPropertyDescriptor(obj, prop)
- if (typeof old[getOrSet] != 'function') {
- console.warn('Risky interceptor for ', fun)
- debugger
- }
- let ifunc = createInterceptorFunction(old[getOrSet], fun, obj)
- let x = {}
- x[getOrSet] = ifunc
- Object.defineProperty(obj, prop, x)
- }
-
- interceptFunction(window.Function.prototype, 'toString', function toString(){
- return orig(toString).apply(funcsToConceil.get(this)||this, arguments)
- })
-
- //image interception
- interceptFunction(window.CanvasRenderingContext2D.prototype, 'drawImage', function drawImage(...args) {
- //do what needs to be done
- let img_source = args[0], img
-
- let oldsrc = dirtyFlag('get:source', this.canvas)
- if (oldsrc && oldsrc != img_source) {
- //source changes are sus, rip to be on the safe side
- captureNewImage(this.canvas, oldsrc)
- dirtyFlag('set+risky', this.canvas)
- }
-
- // if (img_source.toString() == "[object HTMLImageElement]" && img_source.naturalHeight == 0) debugger
- if (args.length == 3 || (
- args.length == 5 &&
- args[1] == 0 &&
- args[2] == 0 &&
- args[3] == img_source.width &&
- args[4] == img_source.height
- )
- ) {
- //no cropping of the source image, or it covers the whole canvas
- if (dirtyFlag('get', this.canvas)) {
- captureNewImage(this.canvas, dirtyFlag('get:source', this.canvas))
- }
- //dirtyFlag('clear', this.canvas) //done by captureNewImage, in theory that should be enough
- let source = img_source
-
- //set scrambling param just in case only part of the image is scrambled
- //make the params compatible with the full length drawImage arguments
- let fullLengthArgs = [
- 0, 0, //source origin
- img_source.width, img_source.height,//source dimensions
- 0, 0, //target origin
- img_source.width, img_source.height //target dimensions
- ]
- dirtyFlag('set+params', this.canvas, img_source, fullLengthArgs)
-
- captureNewImage(this.canvas, img_source)
- } else if (args.length == 9) {
- //need to canvas rip because the image is likely to be scrambled
- dirtyFlag('set+timer+params', this.canvas, img_source, args.slice(1))
- }
- //call the proper function
- return ctxDrawImage.apply(this, args)
- })
-
- function ignoreSource(source) {
- let e = new CustomEvent(OPTIONS.keys.toPageTop, {
- detail: {
- action: 'ignoreSource',
- source: source,
- context: window
- }
- })
- windowtop.dispatchEvent(e)
- }
- interceptFunction(window.HTMLCanvasElement.prototype, 'toBlob', function toBlob() {
- if (dirtyFlag('get', this)) {
- let src = dirtyFlag('get:source', this)
- //If no image made its way to the canvas, then there's no need to capture it
- if (src) captureNewImage(this, src)
- }
- return canvasToBlob.call(this, (b)=>{
- ignoreSource(b)
- arguments[0](b)
- })
- })
- interceptFunction(window.HTMLCanvasElement.prototype, 'toDataURL', function toDataURL() {
- if (dirtyFlag('get', this)) {
- let src = dirtyFlag('get:source', this)
- //If no image made its way to the canvas, then there's no need to capture it
- if (src) captureNewImage(this, src)
- }
- let uri = canvasToDataURL.apply(this, arguments)
- ignoreSource(uri)
- return uri
- })
- interceptFunction(window.CanvasRenderingContext2D.prototype, 'putImageData', function putImageData() {
- dirtyFlag('set+risky+timer', this.canvas)
- const ret = orig(putImageData).apply(this, arguments)
- if (arguments[0].width == this.canvas.width && arguments[0].height == this.canvas.height) {
- captureNewImage(this.canvas, arguments[0])
- }
- return ret
- })
- interceptFunction(window.CanvasRenderingContext2D.prototype, 'createPattern', function createPattern() {
- //capture the image that's passed in but don't link it to this canvas as technically
- //nothing happened just yet and we don't want to reset the dirty flag just yet
- captureNewImage('canvaspattern', arguments[0])
- let pattern = orig(createPattern).apply(this, arguments)
- ignoreSource(pattern)
- return pattern
- })
-
- interceptFunction(window.URL, 'createObjectURL', function createObjectURL() {
- let url = createUrlFromBlob(...arguments)
- let blob = arguments[0]
- urlToBlobMapping[url] = blob
- let e = new CustomEvent(OPTIONS.keys.toPageTop, {
- detail: {
- action: 'urlToBlob',
- url: url,
- blob: blob,
- context: window
- }
- })
- windowtop.dispatchEvent(e)
- if (blob instanceof Blob && blob.type.startsWith('image')) {
- captureNewImage('createObjectURL', blob)
- } else {
- // blob.arrayBuffer().then(a => {
- // let u = new Uint8Array(a)
- // if (
- // (u[0] === 0xFF && u[1] === 0xD8 && u[2] === 0xFF) || //JPG
- // (u[1] === 0x50 && u[2] === 0x4E && u[3] === 0x47) || //PNG
- // (u[8] === 0x57 && u[9] === 0x45 && u[10] === 0x42) || //Web(P)
- // (u[0] === 0x47 && u[1] === 0x49 && u[2] === 0x46) //GIF
- // ) {
- // captureNewImage('createObjectURL', blob)
- // }
- // })
-
- //mime sniffing is clearly insufficient, there's too many image formats to hardcode, and there could be more in the future
- let i = new Image()
- i.onload = ()=> captureNewImage('createObjectURL', blob)
- imgSetSrc.call(i, url)
- }
- return url
- })
- // interceptFunction(window.URL, 'revokeObjectURL', function revokeObjectURL() {
- // return undefined
- // })
- interceptProperty(window.HTMLImageElement.prototype, 'src', 'set', function setSrc() {
- const url = arguments[0]
- if (url && url.startsWith('blob:') || url.startsWith('data:')) {
- captureNewImage(this, url)
- orig(setSrc).apply(this, arguments)
- } else if (OPTIONS.modifyImgSrcLoading && !this.crossOrigin) {
- GM_xmlhttpRequest_asyncWrapper(url).then((resp) => {
- captureNewImage(this, resp.response)
- let u = URL.createObjectURL(resp.response)
- orig(setSrc).call(this, u)
- }).catch((e) => {
- orig(setSrc).apply(this, arguments)
- })
- } else {
- captureNewImage(this, url)
- orig(setSrc).apply(this, arguments)
- }
- })
-
-
- //block APIs useful for fingerprinting / tracking
- interceptFunction(window.CanvasRenderingContext2D.prototype, 'clearRect', function clearRect(){
- if (arguments[2] != this.canvas.width && arguments[3] != this.canvas.height) {
- if (!OPTIONS.trackingProtection) {
- dirtyFlag('set+risky', this.canvas)
- return orig(clearRect).apply(this, arguments)
- } else {
- return
- }
- }
- if (dirtyFlag('get', this.canvas)) {
- let src = dirtyFlag('get:source', this.canvas)
- if (src) captureNewImage(this.canvas, src)
- }
- return orig(clearRect).apply(this, arguments)
- })
- //setting canvas width/height can also clear the canvas
- interceptProperty(window.HTMLCanvasElement.prototype, 'width', 'set', function setWidth(){
- if (dirtyFlag('get', this)) {
- let src = dirtyFlag('get:source', this)
- //If no image made its way to the canvas, then there's no need to capture it
- if (src) captureNewImage(this, src)
- }
- return orig(setWidth).apply(this, arguments)
- })
- interceptProperty(window.HTMLCanvasElement.prototype, 'width', 'set', function setHeight(){
- if (dirtyFlag('get', this)) {
- let src = dirtyFlag('get:source', this)
- //If no image made its way to the canvas, then there's no need to capture it
- if (src) captureNewImage(this, src)
- }
- return orig(setHeight).apply(this, arguments)
- })
- interceptFunction(window.CanvasRenderingContext2D.prototype, 'fillRect', function fillRect(){
- if (arguments[2] != this.canvas.width && arguments[3] != this.canvas.height) {
- if (!OPTIONS.trackingProtection) {
- dirtyFlag('set+risky', this.canvas)
- return orig(fillRect).apply(this, arguments)
- } else {
- return
- }
- }
- if (dirtyFlag('get', this.canvas)) {
- let src = dirtyFlag('get:source', this.canvas)
- //If no image made its way to the canvas, then there's no need to capture it
- if (src) captureNewImage(this.canvas, src)
- }
- if (OPTIONS.arbitraryFillStyle)
- dirtyFlag('set+risky', this.canvas);
- return orig(fillRect).apply(this, arguments)
- })
- interceptFunction(window.CanvasRenderingContext2D.prototype, 'strokeRect', function strokeRect() {
- if (!OPTIONS.trackingProtection) {
- dirtyFlag('set+risky', this.canvas)
- return orig(strokeRect).apply(this, arguments)
- } else {
- return
- }
- })
- interceptFunction(window.CanvasRenderingContext2D.prototype, 'fill', function fill() {
- if (!OPTIONS.trackingProtection) {
- dirtyFlag('set+risky', this.canvas)
- return orig(fill).apply(this, arguments)
- } else {
- return
- }
- })
- interceptFunction(window.CanvasRenderingContext2D.prototype, 'stroke', function stroke() {
- if (!OPTIONS.trackingProtection) {
- dirtyFlag('set+risky', this.canvas)
- return orig(stroke).apply(this, arguments)
- } else {
- return
- }
- })
- //should text be blocked too?
- //it can be useful despite tracking possibility
- //if we block transparency, that shouldn't pose too much of a risk
- interceptProperty(window.CanvasRenderingContext2D.prototype, 'globalAlpha', 'set', function setAlpha(){
- if (OPTIONS.trackingProtection) {
- return orig(setAlpha).call(this, Math.round(arguments[0]))
- } else {
- return orig(setAlpha).call(this, arguments[0])
- }
- })
- interceptProperty(window.CanvasRenderingContext2D.prototype, 'fillStyle', 'set', function setStyle(){
- if (OPTIONS.trackingProtection && !OPTIONS.arbitraryFillStyle) {
- return orig(setStyle).call(this, '#f60')
- } else {
- return orig(setStyle).apply(this, arguments)
- }
- })
- interceptFunction(window.CanvasRenderingContext2D.prototype, 'fillText', function fillText() {
- if (OPTIONS.trackingProtection && !OPTIONS.allowText) {
- return
- } else {
- dirtyFlag('set+risky', this.canvas)
- return orig(fillText).apply(this, arguments)
- }
- })
- interceptFunction(window.CanvasRenderingContext2D.prototype, 'strokeText', function strokeText() {
- if (OPTIONS.trackingProtection && !OPTIONS.allowText) {
- return
- } else {
- dirtyFlag('set+risky', this.canvas)
- return orig(strokeText).apply(this, arguments)
- }
- })
-
-
- // //don't let sites get away by sourcing their functions/prototypes from an iframe
- // interceptProperty(window.HTMLIFrameElement.prototype, 'contentWindow', 'get', function getIFrame(){
- // let iframeWindow = orig(getIFrame).call(this)
- // try {
- // setupIntercept(iframeWindow)
- // } catch (all) {}
- // return iframeWindow
- // })
-
- // ^ should be handled by userscript manager
- }
- setupIntercept(targetWindow)
-
- console.log('cr page script loaded')
- }
- //insert page script into page
- let injectionScript = document.createElement('script')
- // ifr.src = 'about:blank'
- // let s = document.createElement('script')
- let injectionCode = `
- (${pageScript.toString()})(${JSON.stringify(OPTIONS)});
- document.currentScript.remove()
- `;
- let injectionBlob = new Blob([injectionCode], {type:'application/javascript'});
- let injectionUrl = URL.createObjectURL(injectionBlob);
- injectionScript.setAttribute('src', injectionUrl);
- (document.body || document.documentElement || document).insertAdjacentElement('afterbegin', injectionScript);
-
-
- //cross origin iframes will not be able to dispatch events to the top level window.
- //even the content script cannot work around that without being detectable.
- //therefore, we need to add nested menus
- let windowtop = window
- try {
- while (windowtop != window.top) {
- if ('dispatchEvent' in windowtop.parent) {
- windowtop = windowtop.parent
- } else {
- break
- }
- }
- } catch (e) {}
-
- //insert UI and Content script only once on the top level document
- if (window == windowtop) {
-
- function IndexTracker(){
- let values = []
- function getID(value) {
- if (!value) return null
- let i = values.indexOf(value)
- if (i < 0) {
- values.push(value)
- i = values.indexOf(value)
- }
- return i
- }
- return getID
- }
-
-
- let NutZip
- (()=>{
- NutZip=function(){const p="byteLength";async function d(d){const h=d.crcLut;return async function(e,t){var{nameLength:e,data:a}=d.file,r=function(e){let[t,a,r,n,o,s,f,i]=h,c=-1,u=0;for(var l,g,w=new Uint32Array(e.buffer,0,e.buffer.byteLength>>>2),p=4294967294&w.length;u<p;)l=w[u++]^c,g=w[u++],c=i[255&l]^f[l>>>8&255]^s[l>>>16&255]^o[l>>>24]^n[255&g]^r[g>>>8&255]^a[g>>>16&255]^t[g>>>24];let d=4*u;for(;d<e.length;)c=c>>>8^t[255&c^e[d++]];return~c}(a=new Uint8Array(a)),n=t?(o=a,n=new CompressionStream("deflate-raw"),o=new Response(o).body.pipeThrough(n),await new Response(o).arrayBuffer()):a.buffer,o=new ArrayBuffer(30),s=new DataView(o),f=new ArrayBuffer(46),i=new DataView(f),c=(g=new Date).getFullYear(),u=g.getMonth()+1,l=g.getDate(),g=g.getHours()<<11|g.getMinutes()<<5|g.getSeconds()>>>1,c=(c<1980?0:2107<c?127:c-1980)<<9|u<<5|l;let[w,p]=(u=a=>[(e,t)=>a.setUint16(e,t,!0),(e,t)=>a.setUint32(e,t,!0)])(s);return p(0,67324752),t?w(4,2580):w(4,2570),w(6,2048),t?w(8,8):w(8,0),w(10,g),w(12,c),p(14,r),p(18,n.byteLength),p(22,a.byteLength),w(26,e),[w,p]=u(i),p(0,33639248),w(4,2623),t?w(6,2580):w(6,2570),w(8,2048),t?w(10,8):w(10,0),w(12,g),w(14,c),p(16,r),p(20,n.byteLength),p(24,a.byteLength),w(28,e),{data:n,localHeader:o,centralHeader:f}}(0,d.compress)}const h=function(){var e=Array.from({length:8},()=>new Uint32Array(256)),[a,t,r,n,o,s,f,i]=e;for(let e=0;e<=255;e++){let t=e;for(let e=0;e<8;e++)t=t>>>1^3988292384*(1&t);a[e]=t}for(let e=0;e<=255;e++)t[e]=a[e]>>>8^a[255&a[e]],r[e]=t[e]>>>8^a[255&t[e]],n[e]=r[e]>>>8^a[255&r[e]],o[e]=n[e]>>>8^a[255&n[e]],s[e]=o[e]>>>8^a[255&o[e]],f[e]=s[e]>>>8^a[255&s[e]],i[e]=f[e]>>>8^a[255&f[e]];return e}(),y=new TextEncoder;return async function(e,a=!1){const r=e.map(e=>y.encode(e.name).buffer);var t,n,g=d,w=e.map((e,t)=>({args:{file:{data:e=(e="string"==typeof(e=e.data)?y.encode(e):e).buffer&&"object"==typeof e.buffer?e.buffer:e,nameLength:r[t][p]},compress:a,crcLut:h},transfer:[e]})),o=[];let s=0,f=0;for(t of c=await new Promise(r=>{const t=w.length;let n=t,a=-1,o=[],s=[];var e=`${(e=g).toString()};onmessage=async e=>{var a=await ${e.name}(e.data.p.args);let s=[];const t=e=>{if("object"==typeof e){"ArrayBuffer"==e[Symbol.toStringTag]&&s.push(e);for(var a of Object.values(e))t(a)}};t(a),postMessage({r:a,i:e.data.i},s)};`,f=URL.createObjectURL(new Blob([e])),i=Math.min(t,navigator.hardwareConcurrency);const c=e=>{++a<t&&e.postMessage({i:a,p:w[a]},w[a].transfer)};var u=e=>{var t=e.data.i;if(o[t]=e.data.r,0==--n){for(var a of s)a.terminate();r(o)}else c(e.srcElement)};for(let e=0;e<i;++e){var l=new Worker(f);l.onmessage=u,s.push(l),c(l)}}))t.offset=s,o.push(t.localHeader),o.push(r[f]),o.push(t.data),s+=t.localHeader[p]+r[f++][p]+t.data[p];let i=0;f=0;for(n of c)new DataView(n.centralHeader).setUint32(42,n.offset,!0),o.push(n.centralHeader),o.push(r[f]),i+=n.centralHeader[p]+r[f++][p];var c=new ArrayBuffer(22),u=(l=new DataView(c)).setUint32.bind(l),l=l.setUint16.bind(l);return u(0,101010256,!0),l(8,e.length,!0),l(10,e.length,!0),u(12,i,!0),u(16,s,!0),o.push(c),new Blob(o,{type:"application/zip"})}}();
-
- })()
-
- let LOG = '#,action,"origin object id",params\n'
- const logger = (function () {
- if (!LOGGING) return ()=>undefined;
- var logCount = 0
- var origins = new IndexTracker()
- const objToID = (obj, frame) => {
- let frameID = frame
- let i = origins(obj)
- return `#${frameID}/${i}`
- }
- return (title, that, args, frame) => {
- if (!that) that = '';
- if (that.canvas) that = that.canvas;
- let x = objToID(that, frame)
- let argumentArray = Array.from(args).map(
- x => typeof x == 'object' ? objToID(x, frame) : x
- )
- LOG += [
- logCount++,
- title,
- x,
- `"${JSON.stringify(argumentArray).replaceAll('"', '""')}"`
- ].join(',') + "\n";
- if (LOGGING == 'console') console.debug(title, 'on', x, 'with args:', args);
- }
- })()
-
-
-
- let dledImgsCounterElement
- let overlay = document.createElement('tbody')
- document.addEventListener("DOMContentLoaded", (event) => {
- // let divName
- // do {
- // divName = generateKey(3, 10)
- // } while ((document.getElementsByTagName(divName)).length)
- // const div = document.createElement(divName)
- const div = document.createElement('div')
- const shadow = div.attachShadow({mode: 'closed'})
- shadow.innerHTML = `
- <details id="_____cr" style="position: fixed; bottom: 0; left: 0; background-color: white; color: black; font-size: small; z-index: 99999999999999999;max-height:100%;max-width:100%">
- <div style="width: 300px; height: 300px; overflow: scroll">
- <div style="position:sticky;top:0;background:white;z-index:1">
- Bulk <button>download</button> all selected images <br>
- Selection: <button title="Select all found images.">All</button> <button title="Deselect all">None</button> <span title="Select all found images starting with the respective letter."><button>b</button> <button>c</button> <button>d</button> <button>e</button> <button>i</button> <button>p</button></span> <br>
- <details>
- <summary><small>Problems? Click here!</small></summary>
- <small style="padding-left: 1em; display: block;">
- <em> Changes to the below options will require reloading the page to take effect. </em>
- <details>
- <summary><input id="trackingProtection" type="checkbox"> Prevent insertion of tracking data </summary>
- <div style="padding-left: 1em; display: block;">
- This blocks several APIs often used to insert hidden tracking pixels or account-identifying watermarks. Of course, no protection measures can be 100% effective, and this is entirely useless if the site adds tracking data server-side. Also, this could potentially be detected by the website.
- <details>
- <summary><input id="arbitraryFillStyle" type="checkbox"> Allow arbitrary fillStyle</summary>
- Should for some reason images end up entirely orange, try ticking this checkbox. Note that websites might embed hidden tracking pixels this way.
- </details>
- <details>
- <summary><input id="allowText" type="checkbox"> Allow drawing text </summary>
- This poses a big risk of hidden watermark insertion but sometimes text drawn this way can include useful information.
- </details>
- <hr>
- </div>
- </details>
- <details>
- <summary><input id="mergedDownloads" type="checkbox"> Merge split pages (broken)</summary>
- Doesn't work properly at the moment, don't use this. If required, use the Firefox Add-On port of this userscript.
- </details>
- <details>
- <summary><input id="modifyImgSrcLoading" type="checkbox"> Modify <img> loading</summary>
- If images fail to load, cannot be captured or cannot be downloaded, try enabling this option. Intercepts most image loading and routes it through the UserScript manager to bypass CORS restrictions. This should be relatively safe, but could potentially be detected by the website.
- </details>
- <details>
- <summary><button>Download</button> all <img>s currently on the page <span id="dlcounter">(slow)</span></summary>
- Basically like the classic image downloading browser add-ons. Make sure to scroll through the entire page first to make sure all images have actually loaded. Note that this is completely unrelated to the captured image list and other functionality of this UserScript.
- </details>
- <details>
- <summary><button>Save</button> logs of intercepted functions</summary>
- For debugging purposes to investigate what a website might be doing.
- </details>
- </small>
- </details>
- <hr>
- </div>
- <table style="width:100%"></table>
- </div>
- <summary>Show</summary>
- </details>`
- let buttons = shadow.querySelectorAll('button')
- buttons[0].addEventListener('click', dlSelected)
- buttons[1].addEventListener('click', ()=>selectAll(true))
- buttons[2].addEventListener('click', ()=>selectAll(false))
- buttons[3].addEventListener('click', ()=>selectAllOf('b'))
- buttons[4].addEventListener('click', ()=>selectAllOf('c'))
- buttons[5].addEventListener('click', ()=>selectAllOf('d'))
- buttons[6].addEventListener('click', ()=>selectAllOf('e'))
- buttons[7].addEventListener('click', ()=>selectAllOf('i'))
- buttons[8].addEventListener('click', ()=>selectAllOf('p'))
- buttons[9].addEventListener('click', dlAllImgs)
- buttons[10].addEventListener('click', ()=> {
- createDownload('data:text/plain,'+encodeURIComponent(LOG), 'crlog'+(+new Date())+'.csv')
- })
- for (let x in OPTIONS) {
- if (typeof OPTIONS[x] != 'boolean') continue;
- let check = shadow.getElementById(x)
- if (!check) continue;
- check.checked = OPTIONS[x]
- check.addEventListener('change', function(){
- GM_setValue(this.id, this.checked)
- })
- }
- dledImgsCounterElement = shadow.querySelector('#dlcounter')
- shadow.querySelector('table').appendChild(overlay)
- div.style.display = 'block'
- div.style.width = '0'
- div.style.height = '0'
- document.documentElement.appendChild(div)
- });
-
- const canvasToBlob = HTMLCanvasElement.prototype.toBlob
- const ctxDrawImage = CanvasRenderingContext2D.prototype.drawImage
- const canvasToDataURL = HTMLCanvasElement.prototype.toDataURL
- const createUrlFromBlob = URL.createObjectURL
- const imgSetSrc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src').set
- function ctob(canvas, ...args) {
- return new Promise(function(resolve) {
- canvasToBlob.apply(canvas, [resolve, ...args])
- })
- }
-
- const previewImageSize = 50
-
- const capturedImages = new Map()
- const ignoredSources = []
- let globalImageCounter = 0
- const captureNewImage = (function() {
- function copyImage(image) {
- if (typeof image == 'string') {
- return copyToImg(image)
- }
- switch (image.toString()) {
- case "[object HTMLImageElement]":
- return copyToImg(image.src)
- break
- case "[object Blob]":
- return copyToImg(createUrlFromBlob(image))
- break
- case "[object HTMLCanvasElement]":
- default:
- return copyToCanvas(image)
- }
- }
- function copyToImg(url) {
- let img = new Image()
- img.style.maxWidth = previewImageSize+'px'
- img.style.maxHeight = previewImageSize+'px'
- imgSetSrc.call(img, url)
- return img
- }
- function copyToCanvas(image, scramble) {
- if (!(scramble?.compare?.size > 1))
- scramble = {};
- let c = document.createElement('canvas')
- c.width = scramble.w || image.naturalWidth || image.width
- c.height = scramble.h || image.naturalHeight || image.height
- c.style.maxWidth = previewImageSize+'px'
- c.style.maxHeight = previewImageSize+'px'
- let ctx = c.getContext('2d')
- if (scramble.compare && scramble.compare.size) {
- ctxDrawImage.call(ctx, image, scramble.x, scramble.y, c.width, c.height, 0, 0, c.width, c.height)
- } else {
- ctxDrawImage.call(ctx, image, 0, 0)
- }
- return c
- }
- function processScrramblingParams(params, thingToSave) {
- if (!params) params = [];
- const tTS_w = thingToSave.naturalWidth || thingToSave.width
- const tTS_h = thingToSave.naturalHeight || thingToSave.height
- let bounds = [Infinity, Infinity, -Infinity, -Infinity]
- for (let x of params) {
- bounds[0] = Math.min(bounds[0], x[4])
- bounds[1] = Math.min(bounds[1], x[5])
- bounds[2] = Math.max(bounds[2], x[4]+x[6])
- bounds[3] = Math.max(bounds[3], x[5]+x[7])
- }
- bounds[0] = Math.max(bounds[0], 0)
- bounds[1] = Math.max(bounds[1], 0)
- bounds[2] = Math.min(bounds[2], tTS_w)
- bounds[3] = Math.min(bounds[3], tTS_h)
- return {
- w: bounds[2] - bounds[0],
- h: bounds[3] - bounds[1],
- x: bounds[0],
- y: bounds[1],
- compare: new Set((params || []).map(x => x.slice(0, 4).join()))
- }
- }
- function mergeImages(...images) {
- let yOffset = 0
- if (OPTIONS.binbMerging) {
- yOffset = -4
- }
- let c = document.createElement('canvas')
- c.width = Math.max(...images.map(x => x.naturalWidth || x.width))
- c.height = images.reduce((a,x) => a + (x.naturalHeight || x.height), 0) + (images.length -1)*yOffset
- c.style.maxWidth = previewImageSize+'px'
- c.style.maxHeight = previewImageSize+'px'
- let ctx = c.getContext('2d')
- let y = 0
- for (let i of images) {
- let x = Math.floor((c.width - (i.naturalWidth || i.width))*0.5)
- ctxDrawImage.call(ctx, i, x, y)
- y += (i.naturalHeight || i.height) + yOffset
- }
- return c
- }
- function addPageToPile(obj) {
- //image = element the image was caught on, if applicable
- //source = the source object/element that was captured
- let {
- image,
- source,
- risky,
- scrambleParams,
- context
- } = obj
-
- let isImageData = false, isScrambled = Boolean(scrambleParams && scrambleParams.length > 1), isFiltered = false
- globalImageCounter++
- if (!source && image.toString() == "[object HTMLImageElement]") {
- source = image.src
- }
- if (source.toString() == "[object HTMLImageElement]") {
- source = source.src
- }
- if (source.toString() == "[object HTMLCanvasElement]") {
- //carry over flags
- risky |= dirtyFlag('get:risky', source)
- }
- if (image.toString() == "[object HTMLCanvasElement]") {
- if (image.filter && image.filter != 'none') {
- isFiltered = true
- }
- }
- if (typeof source == 'string' && source.startsWith('blob:')) {
- source = urlToBlobMapping[source]
- }
- //no idea how to effectively dedupe ImageData
- if (source.toString() == "[object ImageData]") {
- //let's not keep that in memory too when we already never delete blobs
- source = 'imagedata-' + globalImageCounter
- isImageData = true
- }
- //there's probably more possible source types that I forgot, who cares
- if (ignoredSources.includes(source)) return false;
-
- const thingToSave = (isScrambled || isImageData || isFiltered) ? image : source
- let scramble = processScrramblingParams(scrambleParams, thingToSave)
-
- let existing = capturedImages.get(source)
- if (!existing) {
- //the template for a captured image entry
- let obj = {
- isScrambled: isScrambled,
- individual: [{
- savedImage: null,
- scrambleParams: scramble.compare,
- caughtOn: [image],
- isRisky: !!risky
- }],
- combined: {}
- }
- let i = obj.individual[0]
- if (isScrambled) {
- let c = copyToCanvas(thingToSave, scramble)
- i.savedImage = c
- } else {
- i.savedImage = copyImage(thingToSave)
- }
- capturedImages.set(source, obj)
- obj.combined = i
- return true
- } else {
- if (isScrambled) {
-
- //compare if the scrambleParams are the same
- let exIdx
- if (
- existing.isScrambled &&
- (exIdx = existing.individual.findIndex(x => x.scrambleParams.isSubsetOf(scramble.compare))) >= 0
- ) {
- //same params were captured once already
- let exI = existing.individual[exIdx].savedImage
- if (exI.width >= scramble.w && exI.height >= scramble.h) {
- return false
- } else {
- //previous capture is likely to be incomplete, remove it and capture anew
- existing.individual.splice(exIdx, 1)
- }
- }
-
- let c = copyToCanvas(thingToSave, scramble)
-
- if (existing.isScrambled) {
- if (OPTIONS.mergedDownloads) {
- let merged = mergeImages(existing.combined.savedImage, c)
- let combi = {
- savedImage: merged,
- scrambleParams: '',
- caughtOn: existing.combined.caughtOn.slice(),
- isRisky: !!risky || existing.combined.risky
- }
- if (!combi.caughtOn.includes(image)) combi.caughtOn.push(image);
- existing.combined = combi
- }
- existing.individual.push({
- savedImage: c,
- scrambleParams: scramble.compare,
- caughtOn: [image],
- isRisky: !!risky
- })
- } else {
- //the still scrambled image was saved. discard it in favor of the now uncrambled addition
- let obj = existing.combined //for whole images like the still scrambled page this should be the same object as individual[0]
- obj.savedImage = copyImage(c)
- obj.scrambleParams = scramble.compare.union(obj.scrambleParams)
- if (!obj.caughtOn.includes(image)) obj.caughtOn.push(image);
- obj.isRisky = !!risky || obj.isRisky
- existing.isScrambled = true
- }
- return true
- } else {
- //probably the same thing, already exists
- if (!existing.combined.caughtOn.includes(image))
- existing.combined.caughtOn.push(image);
- if (scramble.compare.size == 1)
- existing.combined.scrambleParams = scramble.compare.union(existing.combined.scrambleParams);
- return true
- }
- }
- }
- return function(obj) {
- // console.log('captured Image:', obj)
- addPageToPile(obj) && updateOverlay()
- }
- })()
-
- function updateOverlay() {
- let sourcedFrom = {
- i: [], //normal urls caught on img
- u: [], //blob urls caught on img
- c: [], //drawImage interception on canvases
- e: [], //scrambled images first captured as normal img (E like scrambled Eggs)
- d: [], //ImageData interception on canvases
- p: [], //createPattern interception
- b: [] //createObjectURL interception
- }
- for (let x of capturedImages.entries()) {
- if (typeof x[0] == 'string') {
- if (x[0].startsWith('imagedata')) {
- sourcedFrom.d.push(x)
- } else {
- if (x[1].isScrambled) {
- sourcedFrom.e.push(x)
- } else {
- sourcedFrom.i.push(x)
- }
- }
- } else {
- if (typeof x[1].combined.caughtOn[0] == 'string') {
- if (x[1].combined.caughtOn[0] == 'canvaspattern')
- sourcedFrom.p.push(x);
- else sourcedFrom.b.push(x)
- } else {
- if (x[1].combined.caughtOn[0] instanceof HTMLCanvasElement) {
- sourcedFrom.c.push(x)
- } else {
- sourcedFrom.u.push(x) //should in theory remain empty as any such image should have gone thorugh createObjectURL prior
- }
- }
- }
- }
- let docHTML = document.documentElement.innerHTML
- let b = sourcedFrom['b'].map(x=>({
- /* Looks up the corresponding blob URL, and finds it in the page HTML */
- i: docHTML.indexOf( Object.keys(urlToBlobMapping)[Object.values(urlToBlobMapping).indexOf(x[0])] ),
- x: x
- }))
- b.sort((a,b)=> a.i > b.i )
- sourcedFrom['b'] = b.map(x=>x.x)
- let allCanvases = Array.from(document.getElementsByTagName('canvas'))
- let allImgs = Array.from(document.getElementsByTagName('img'))
- overlay.innerHTML = ''
- for (let cat in sourcedFrom) {
- let offscreenCounter = 1
- for (let x of sourcedFrom[cat]) {
- let name = cat
- let origin = x[1].combined.caughtOn.find(x=>(x instanceof HTMLImageElement || x instanceof HTMLCanvasElement) && x.parentElement != null)
- let allOfThem = origin && origin instanceof HTMLImageElement ? allImgs : allCanvases
- let n = 0
- if (origin && (n = allOfThem.findIndex(node => node.isSameNode(origin)) + 1) && allOfThem.length >= sourcedFrom[cat].length) {
- name += String(n).padStart(4, '0')
- } else {
- name += '_' + String(offscreenCounter++).padStart(4, '0')
- }
- //TODO do something with the individual vs combined images
- let y;
- if (OPTIONS.mergedDownloads) {
- y = [x[1].combined]
- } else {
- y = x[1].individual
- }
- for (let i = 0; i < y.length; i++) {
- let name2 = name, fileInfo = ''
- if (y.length > 1) {
- name2 += '-' + String(i+1).padStart(2, '0')
- } else {
- if (x[0] instanceof Blob && x[0].type) {
- fileInfo = extensionFromMimeType(x[0].type)
- } else if (typeof x[0] == 'string' && x[0].startsWith('imagedata')) {
- fileInfo = '.png'
- }
- }
- let z = y[i].savedImage
- let riskBg = y[i].isRisky ? 'background: #ffc;' : ''
- overlay.insertAdjacentHTML('beforeend', `<tr style="height: ${previewImageSize+5}px; ${riskBg}">
- <td><input type="checkbox"></td>
- <td style="max-width: ${previewImageSize}px; max-height: ${previewImageSize}px; position: relative"></td>
- <td>${name2}</td>
- <td>${fileInfo}</td>
- <td title="Download this image."><button>DL</button></td>
- </tr>`)
- let tds = overlay.lastChild.children
- if (z) tds[1].appendChild(z)
- tds[4].addEventListener('click', dl)
- }
- }
- }
- }
- function selectAll(check = true) {
- for (let x of overlay.children) {
- x.children[0].firstChild.checked = check
- }
- }
- function selectAllOf(type) {
- for (let x of overlay.children) {
- x.children[0].firstChild.checked = x.children[2].innerText.startsWith(type)
- }
- }
-
- function extensionFromMimeType(mime) {
- let extension
- switch (mime) {
- case 'image/png':
- extension = '.png'
- break;
- case 'image/webp':
- extension = '.webp'
- break;
- case 'image/gif':
- extension = '.gif'
- break;
- case 'image/avif':
- extension = '.avif'
- break;
- case 'image/jxl':
- extension = '.jxl'
- break;
- case 'image/svg+xml':
- extension = '.svg'
- break;
- default:
- extension = '.jpeg'
- break
- }
- return extension
- }
- function createDownload(url, filename) {
- let a = document.createElement('a')
- a.href = url
- a.download = filename
- a.click()
- }
- function GM_xmlhttpRequest_asyncWrapper (urlToFetch) {
- return new Promise((resolve, reject) => {
- let url = new URL(urlToFetch, location.href)
- let sameOrigin = urlToFetch.startsWith(location.origin)
- GM_xmlhttpRequest({
- url: url.href,
- responseType: 'blob',
- anonymous: false,
- headers: {
- 'Referer': location.origin + '/',
- 'Sec-Fetch-Dest': 'image',
- 'Sec-Fetch-Mode': 'no-cors',
- 'Sec-Fetch-Site': sameOrigin ? 'same-origin' : 'cross-site',
- 'Pragma': 'no-cache',
- 'Cache-Control': 'no-cache'
- },
- onload: resolve,
- onerror: reject
- })
- })
- }
- async function fetchImg(url) {
- if (url.startsWith('http')) {
- let r = await GM_xmlhttpRequest_asyncWrapper(url)
- let mime = r.responseHeaders.match(/content-type: (.*)/i)
- return {
- data: await r.response,
- contentType: mime && mime[1]
- }
- } else {
- let r = await fetch(url, {cache: 'force-cache'})
- return {
- data: await r.blob(),
- contentType: r.headers.get('content-type')
- }
- }
- }
-
- async function dl() {
- const url = this.parentElement.children[1].firstChild.src
- console.log(this, url)
- let img = await fetchImg(url)
- let extension = extensionFromMimeType(img.contentType)
- let objurl = createUrlFromBlob(img.data)
- createDownload(objurl, this.parentElement.children[2].innerText + extension)
- }
-
- async function dlSelected() {
- let files = []
- let filenames = []
- // for (let x of overlay.children) {
- // if (x.children[0].firstChild.checked) {
- // try {
- // let img, extension
- // if (x.children[1].firstChild.toString() == "[object HTMLImageElement]") {
- // const url = x.children[1].firstChild.src
- // let req = await fetchImg(url)
- // extension = extensionFromMimeType(req.contentType)
- // img = req.data
- // } else if (x.children[1].firstChild.toString() == "[object HTMLCanvasElement]") {
- // img = await ctob(x.children[1].firstChild)
- // extension = '.png'
- // }
- // let filename = x.children[2].innerText
- // let filenameCount = filenames.reduce((a,x)=>a+(x==filename?1:0), 0)
- // filenames.push(filename)
- // if (filenameCount) filename += ' ('+(filenameCount+1)+')';
- // files.push({
- // name: filename + extension,
- // data: await img.arrayBuffer()
- // })
- // } catch (e) {}
- // }
- // }
- await Promise.allSettled(Array.from(overlay.children).map(async x => {
- if (x.children[0].firstChild.checked) {
- try {
- let img, extension
- if (x.children[1].firstChild.toString() == "[object HTMLImageElement]") {
- const url = x.children[1].firstChild.src
- let req = await fetchImg(url)
- extension = extensionFromMimeType(req.contentType)
- img = req.data
- } else if (x.children[1].firstChild.toString() == "[object HTMLCanvasElement]") {
- img = await ctob(x.children[1].firstChild)
- extension = '.png'
- }
- let filename = x.children[2].innerText
- let filenameCount = filenames.reduce((a,x)=>a+(x==filename?1:0), 0)
- filenames.push(filename)
- if (filenameCount) filename += ' ('+(filenameCount+1)+')';
- files.push({
- name: filename + extension,
- data: await img.arrayBuffer()
- })
- } catch (e) {}
- };
- }))
- let zipData = await NutZip(files)
- let blob = new Blob([zipData], {type: "octet/stream"})
- var url = createUrlFromBlob(blob);
- createDownload(url, (+new Date())+'.zip')
- }
-
- async function dlAllImgs() {
- let promises = []
- let filenameCounter = 0
- let fileCompletedCounter = 0
- dledImgsCounterElement.innerText = '(0 imgs)'
- async function getImg(x, i) {
- const url = x.getAttribute('data-original') || x.getAttribute('data-src') || x.getAttribute('content')
- let img
- try {
- img = await fetchImg(url)
- } catch (e) {
- img = await fetchImg(x.src)
- }
- let extension = extensionFromMimeType(img.contentType)
- let filename = String(i).padStart(4, '0')
- dledImgsCounterElement.innerText = `(${++fileCompletedCounter} imgs)`
- return {
- name: filename + extension,
- data: await img.data.arrayBuffer()
- }
- }
- for (let x of document.getElementsByTagName('img')) {
- promises.push(getImg(x, ++filenameCounter))
- }
- Promise.allSettled(promises).then((p) => {
- let files = []
- p.map(x => (x.status == 'fulfilled') && files.push(x.value))
- let zipData = SimpleZip.GenerateZipFrom(files)
- let blob = new Blob([zipData], {type: "octet/stream"})
- var url = createUrlFromBlob(blob);
- createDownload(url, (+new Date())+'.zip')
- dledImgsCounterElement.innerText = ''
- })
- }
-
-
-
- const urlToBlobMapping = {}
-
-
- window.addEventListener(OPTIONS.keys.toContext, (e) => {
- switch (e.detail.action) {
- case 'log':
- logger(e.detail.title, e.detail.that, e.detail.args, e.detail.context)
- break
- case 'captureImage':
- captureNewImage(e.detail)
- break
- case 'urlToBlob':
- urlToBlobMapping[e.detail.url] = e.detail.blob
- break
- case 'ignoreSource':
- ignoredSources.push(e.detail.source)
- break
- }
- })
-
- }
-
- console.log('cr loaded')
- })()