// ==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
// ==/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 += "<p>" + text + "</p>";
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;
}
})();