// ==UserScript== // @name Bing Image Creator auto-download // @namespace http://tampermonkey.net/ // @version 0.011 // @license MIT // @description Automatic image downloader for Bing Image Creator. // @match https://copilot.microsoft.com/images/create*?*autosavetimer=* // @grant GM_download // @require http://code.jquery.com/jquery-3.7.1.min.js // @downloadURL none // ==/UserScript== // // I just pasted this together from things found scattered around the internet. Primarily: https://github.com/Emperorlou/MidJourneyTools // // To enable periodic downloading of newly-created images, go to a 'recent creations' page, and add "&autosavetimer=60" to the URL; // something like: `https://www.bing.com/images/create/-/1234?autosavetimer=60`. // // This implementation is designed to be left unattended - periodically reloading itself. If you click a link it will disable the script, // unless you remove `?*autosavetimer=*` from `@match` above. (function() { 'use strict'; const filename_prefix = "bing/"; const downloadables = "img[src$='&pid=ImgGn']"; const downloadInterval = 600; var pollRate = 60000; var activeDownloads = 0; var loadErrors = 0; var lastReload = 0; var sourceContent; $(document).ready(() => { var new_images = document.createElement("div"); new_images.setAttribute("id", "scanbuffer"); new_images.setAttribute("hidden", ""); document.body.append(new_images); var dialog = document.createElement("dialog"); dialog.setAttribute("id", "logmessage"); dialog.setAttribute("style", "z-index: 100;"); document.body.append(dialog); logger("Automatic image downloader is active."); // TODO: check for expired busyImage-* tags and retry them // TODO: Try to figure this out dynamically: sourceContent = location.href + " #girrc"; var params = new URLSearchParams(window.location.search); pollRate = (params.get('autosavetimer') || 60) * 1000; setTimeout(reload, 1000); lastReload = Date.now(); setInterval(function() { const timeout = pollRate * 5 + 20 * downloadInterval; if (Date.now() - lastReload > timeout) { console.log("Reload function seems to have stopped."); reload(); } }, pollRate * 1.5); }); // sample: https://tse4.mm.bing.net/th?id=OIG2.AbCdEfGhIjKlMnOp123.&w=100&h=100&c=6&o=5&pid=ImgGn function get_img_id(src) { var url = new URL(src); var id = url.searchParams.get('id') || url.pathname.split('/').pop(); if (id == null || id.length < 20) { console.log("couldn't parse image id from:", src, " got:", id); } return id; } // sample: /images/create/kebab-case-prompt/1-0123456789abcedf0123456789abcdef?FORM=GUH2CR // https://copilot.microsoft.com/images/create?q=prompt%20with%20spaces&rt=4&FORM=GENCRE&id=1-0123456789abcedf0123456789abcdef function get_page_id(ref) { var url = new URL(ref); var id = url.searchParams.get('id') || url.searchParams.get('pageId'); if (id == null) { var path = url.pathname.split('/'); while (path.length && path.shift() != 'create') ; if (path.length == 2 && path[1].length >= 32) id = path[1]; } if (id == null) { console.log("couldn't parse referrer id from:", ref); } return id; } // sample: /images/create/kebab-case-prompt/1-0123456789abcedf0123456789abcdef?FORM=GUH2CR function get_page_prompt(ref) { var url = new URL(ref); var q = url.searchParams.get('q'); if (q == null) { var path = url.pathname.split('/'); while (path.length && path.shift() != 'create') ; if (path.length == 2 && path[1].length >= 32) q = path[0]; } if (q == null) { console.log("couldn't parse referrer prompt from:", ref); } return q; } function make_filename(img, src, ref) { var src_filename = get_img_id(src); var desc = get_page_prompt(ref) || img.getAttribute("alt", "image"); var pageid = get_page_id(ref) || "page"; return filename_prefix + src_filename + "_" + pageid + "_" + desc + ".jpg"; } function logger(text) { var status = $("#logmessage")[0]; if (text) { status.innerHTML += "
" + text + "
"; status.show(); } else { status.innerHTML = ""; status.close(); } } function reload() { logger("Rescanning..."); if (activeDownloads > 0) { logger("There are " + activeDownloads + " outstanding."); } var target = $("#scanbuffer"); var result = target.load(sourceContent, function(response, status, xhr) { var delay = 100; if ( status == "error" ) { console.log("problem loading content:", response, status, xhr); logger(null); if (loadErrors > 0) { logger("previous failures: " + loadErrors); } logger("problem doing rescan: " + status + ": " + response); logger("xhr: " + xhr); loadErrors++; } else { loadErrors = 0; var allImages = $(target.find(downloadables).get().reverse()); if (allImages.length < 10) { console.log("Scan buffer doesn't have many images. Is something wrong?"); console.log("all images:", $(target.find("img").get().reverse())); } for (const img of allImages) { const src = get_download_url(img); if (isUrlReady(src)) { const ref = get_href(img) || "https://www.example.com/"; const filename = make_filename(img, src, ref); downloadFile(delay, src, filename, ref); delay += downloadInterval; } } setTimeout(function() { if (activeDownloads == 0) { logger(null); } }, 300); } setTimeout(reload, pollRate + delay); }); lastReload = Date.now(); } function downloadFile(delay, url, filename, referrer) { setUrlBusy(url, filename, referrer); logger("downloading: " + url + " as " + filename); logger(" referrer: " + referrer + ", in " + delay + "ms"); setTimeout(function() { const download = GM_download({ url: url, name: filename, saveAs: false, conflictAction: "uniquify", onload: function () { setUrlSaved(url); }, onerror: function () { logger("error downloading: " + url); clearUrlBusy(url, "download error"); }, ontimeout: function () { logger("timeout downloading: " + url); clearUrlBusy(url, "download timeout"); } }); }, delay); }; function get_download_url(img) { var url = new URL(img.attributes.src.nodeValue); url.searchParams.delete("w"); url.searchParams.delete("h"); url.searchParams.delete("c"); url.searchParams.delete("o"); return url.href; } function get_href(elem) { while (elem) { if (elem.hasAttribute('href')) return elem.href; elem = elem.parentElement; } return null; } function mk_img_id(pfx, src) { return pfx + "-" + get_img_id(src); } function setUrlBusy(src, filename, referrer) { const id = mk_img_id("busyImage", src); // localStorage.setItem(id, { "time": Date.now(), "src": src, "filename": filename, "referrer": referrer }); localStorage.setItem(id, Date.now()); activeDownloads++; } function clearUrlBusy(src, why) { const id = mk_img_id("busyImage", src); console.log("removing busy:", id, localStorage.getItem(id), "because:", why); localStorage.removeItem(id); activeDownloads--; if (activeDownloads == 0) { setTimeout(function() { if (activeDownloads == 0) logger(null); }, 1000); } else if (activeDownloads < 0) { logger("Oops, download count underflow!"); activeDownloads = 0; } } function setUrlSaved(src) { const id = mk_img_id("savedImage", src); localStorage.setItem(id, true); clearUrlBusy(src, "saved"); } function isUrlSaved(src) { const id = mk_img_id("savedImage", src); return localStorage.getItem(id) === "true" ? true : false; } function isUrlReady(src) { if (!src || isUrlSaved(src)) return false; const id = mk_img_id("busyImage", src); const stamp = localStorage.getItem(id); if (!stamp) return true; if (Date.now() - stamp > 60000) { logger("file has been busy too long (lost event?): " + src); clearUrlBusy(src, "stale"); return true; } console.log("still waiting to finish:", src); return false; } })();