Skip to content

Instantly share code, notes, and snippets.

@BrokenEagle
Last active September 10, 2022 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BrokenEagle/669ac9d84bb31c607451927b2623945c to your computer and use it in GitHub Desktop.
Save BrokenEagle/669ac9d84bb31c607451927b2623945c to your computer and use it in GitHub Desktop.
Client-side option for downloading images and uploading them to Danbooru.
// ==UserScript==
// @name ProxyUploader
// @namespace https://gist.github.com/BrokenEagle
// @version 4.0
// @description Shows the tag changes made since the original upload.
// @source https://danbooru.donmai.us/users/23799
// @author BrokenEagle
// @match *://*.donmai.us/uploads/*
// @exclude /^https?://\w+\.donmai\.us/.*\.(xml|json|atom)(\?|$)/
// @grant GM.xmlHttpRequest
// @run-at document-end
// @downloadURL https://gist.github.com/BrokenEagle/669ac9d84bb31c607451927b2623945c/raw/ProxyUploader.user.js
// @updateURL https://gist.github.com/BrokenEagle/669ac9d84bb31c607451927b2623945c/raw/ProxyUploader.user.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js
// @require https://cdn.jsdelivr.net/npm/xregexp@5.1.0/xregexp-all.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/core.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/lib-typedarrays.js
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/md5.min.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/module.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/debug.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/utility.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/validate.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/storage.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/notice.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/network.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/danbooru.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/load.js
// @connect donmai.us
// @connect pximg.net
// @connect pixiv.net
// ==/UserScript==
// eslint-disable-next-line no-redeclare
/* global $ _$ XRegExp localforage Danbooru JSPLib CryptoJS GM unsafeWindow */
/****Global variables****/
//Variables for load.js
const PROGRAM_LOAD_REQUIRED_VARIABLES = ['unsafeWindow.jQuery', 'window.Danbooru'];
const PROGRAM_LOAD_OPTIONAL_SELECTORS = ['#c-uploads #a-new', '#c-uploads #a-show'];
//Program name constants
const PROGRAM_NAME = 'ProxyUploader';
const PROGRAM_CLICK = 'click.pu';
//Main program variable
const PU = {};
//CSS constants
const PROGRAM_CSS = `
#pu-menu {
text-align: center;
}
#pu-menu > div {
display: inline-block;
border: 1px dotted #CCC;
width: 100em;
padding: 1em;
margin: 1em;
}
#pu-display {
margin-bottom: 1em;
}
.pu-image-preview {
display: inline-block;
width: 304px;
height: 300px;
}
.pu-image-preview img {
max-width: 300px;
max-height: 300px;
margin-bottom: 0.25em;
}
.pu-image-download-progress,
.pu-image-upload-progress {
height: 1em;
margin: 0 1em;
}
#pu-download-progress .ui-progressbar-value,
.pu-image-download-progress .ui-progressbar-value {
background-color: lightblue;
}
#pu-download-progress .ui-progressbar-complete,
.pu-image-download-progress .ui-progressbar-complete {
background-color: limegreen;
}
#pu-upload-progress .ui-progressbar-value,
.pu-image-upload-progress .ui-progressbar-value {
background-color: darkgrey;
}
#pu-upload-progress .ui-progressbar-complete,
.pu-image-upload-progress .ui-progressbar-complete {
background-color: green;
}
.pu-image-download-progress.pu-progress-warning .ui-progressbar-complete {
background-color: orange;
}
.pu-image-download-progress.pu-progress-error .ui-progressbar-complete {
background-color: red;
}
#pu-progress {
margin-bottom: 1em;
}
.pu-progressbar {
width: 80%;
height: 30px;
margin: 0 auto;
}
.pu-progress-text {
font-size: 20px;
}
#pu-preview img {
max-width: 100%;
max-height: 80vh;
}
#pu-control {
display: inline-block;
background-color: teal;
border: 1px solid grey;
border-radius: 25px;
font-size: 24px;
margin-bottom: 0.5em;
}
#pu-control:hover {
filter: brightness(1.2);
}
#pu-control a {
color: white;
padding: 0.5em 4em;
padding: -0.5em;
}
#pu-proxy-url {
width: 80em;
}`;
//HTML constants
const PROXY_CONTROLS = `
<div id="pu-menu">
<div>
<div id="pu-display">
</div>
<div id="pu-control">
<a href="#">Proxy Upload</a>
</div>
<div class="pu-input input">
<input type="text" name="proxy_url" id="pu-proxy-url">
</div>
</div>
</div>`;
const SINGLE_PROXY_DISPLAY = `
<div id="pu-single-display">
<div id="pu-progress" style="display: none;">
<h2>Downloading</h2>
<div id="pu-download-progress" class="pu-progressbar">
<div class="pu-progress-text">0%</div>
</div>
<h2>Uploading</h2>
<div id="pu-upload-progress" class="pu-progressbar">
<div class="pu-progress-text">0%</div>
</div>
</div>
<div id="pu-preview" style="display: none;"><img></div>
</div>`;
const RELATED_TAG_COLUMN = `<div class="source-related-tags-columns">
<div class="tag-column translated-tags-related-tags-column" style="width: 18em;">
<h3 class="flex items-center space-x-1">
<input type="checkbox" class="invisible">
<span>Translated Tags</span>
</h3>
<ul class="tag-list simple-tag-list">
%s
</ul>
</div>
</div>`;
const TAG_SELECTOR = `
<li class="flex items-center space-x-1">
<input type="checkbox" tabindex="-1">
<a class="search-tag tag-type-%CATEGORY%" data-tag-name="%NAME%" href="/posts?tags=%ENCODED_NAME%">%NAME%</a>
</li>`;
//Regexes
const PX_IMAGE_RG = XRegExp.tag('x')`
^https?://[^.]+\.pximg\.net # Hostname
(?:/c/\w+)? # Size 1
/(?:img-original|img-master|custom-thumb) # Path
/img
/(?<date>\d{4}/\d{2}/\d{2}/\d{2}/\d{2}/\d{2}) # Date
/(?<id>\d+)_ # ID
p(?<order>\d+) # Order
(?:_(?:master|square|custom)1200)? # Size 2
\.(?<ext>jpg|png|gif|mp4|zip) # Extension
`;
const PX_PAGE_RG = XRegExp.tag('x')`
^https?://www\.pixiv\.net # Hostname
(?:/en)? # Size
/(?:artworks) # Path
/(?<id>\d+) # ID
`;
const FILENAME_RG = XRegExp.tag('x')`
^file://
(?<id>\d+)
-
(?<name>
(?<stem>[^.]+)
\.
(?<ext>jpg|png)
)$
`;
const COMMON_HASHTAG_REGEXES = [
/生誕祭\d*$/,
/誕生祭\d*$/,
/版もうひとつの深夜の真剣お絵描き60分一本勝負(?:_\d+$|$)/,
/版深夜の真剣お絵描き60分一本勝負(?:_\d+$|$)/,
/深夜の真剣お絵描き60分一本勝負(?:_\d+$|$)/,
/版深夜のお絵描き60分一本勝負(?:_\d+$|$)/,
/版真剣お絵描き60分一本勝負(?:_\d+$|$)/,
/版真剣お絵描き60分一本勝(?:_\d+$|$)/,
/版お絵描き60分一本勝負(?:_\d+$|$)/,
];
//Site constants
let SITE_HEADERS = {
'pximg.net': {
Referer: 'https://www.pixiv.net',
},
'sinaimg.cn': {
Referer: 'https://weibo.com',
},
'yande.re': {
Referer: 'yande.re',
},
'konachan.com': {
Referer: 'konachan.com',
},
};
const HANDLED_IMAGES = [{
regex: PX_IMAGE_RG,
format: `https://i.pximg.net/img-original/img/%date%/%id%_p%order%.%ext%`,
handler: (image_match) => GetPixivData(image_match),
}];
const HANDLED_PAGES = [{
regex: PX_PAGE_RG,
format: `https://www.pixiv.net/en/artworks/%id%`,
handler: (page_match) => GetPixivData(page_match),
}];
//Other constants
const MIME_TYPES = {
jpg: 'image/jpeg',
gif: 'image/gif',
png: 'image/png',
};
const API_STORAGE = localforage.createInstance({
name: 'API storage',
driver: [
localforage.INDEXEDDB,
]
});
/****Functions****/
//Library functions
JSPLib.utility.fixedEncodeURIComponent = function (str) {
return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
return '%' + c.charCodeAt(0).toString(16);
});
};
JSPLib.load._isVariableDefined = function(variable_name) {
let variable_hierarchy = variable_name.split('.');
let window_name = null;
if (variable_hierarchy[0] === 'window' || variable_hierarchy[0] === 'unsafeWindow') {
window_name = variable_hierarchy.shift();
}
let curr_obj = (window_name === 'unsafeWindow' ? unsafeWindow : JSPLib._window);
for (let i = 0;i < variable_hierarchy.length;i++) {
if (!(variable_hierarchy[i] in curr_obj)) {
return false;
}
curr_obj = curr_obj[variable_hierarchy[i]];
if ((typeof curr_obj !== 'object' || curr_obj == null) && (i < (variable_hierarchy.length - 1))) {
return false;
}
}
return true;
};
JSPLib.network.ajax = function (self, url, options) {
if (!JSPLib._jquery_installed) {
return Promise.resolve(null);
}
if (JSPLib.validate.isHash(url)) {
options = url;
url = null;
} else if (JSPLib.validate.isString(url)) {
options ||= {};
} else {
JSPLib.utility.throwError("JSPLib: Invalid ajax settings.");
}
self.debug('logLevel', {url, options}, JSPLib.debug.VERBOSE);
if (url) {
return JSPLib._jQuery.ajax(url, options);
} else {
return JSPLib._jQuery.ajax(options);
}
};
JSPLib.network.GM_XHR = function () {
const open_params = ['type', 'url', 'async', 'username', 'password'];
Object.assign(this, {headers: {}}, ...open_params.concat(['status', 'readyState']).map((key) => ({[key]: null})));
this.abort = function() {
this.readyState = 0;
};
this.getAllResponseHeaders = function() {
return (this.readyState !== 4 ? "" : this.responseHeaders);
};
this.getResponseHeader = function(name) {
var regexp = new RegExp('^' + name + ': (.*)$', 'im');
var match = regexp.exec(this.responseHeaders);
return (match ? match[1] : '');
};
this.open = function(...outerargs) {
let context = this;
open_params.forEach((arg, i) => {
context[arg] = outerargs[i] || null;
});
this.readyState = 1;
};
this.setRequestHeader = function(name, value) {
this.headers[name] = value;
};
this.onresponse = function (handler) {
let context = this;
return function (resp) {
['readyState', 'responseHeaders', 'responseText', 'response', 'status', 'statusText', 'lengthComputable', 'loaded', 'total'].forEach((key) => {
context[key] = resp[key];
});
if (context[handler]) {
context[handler](context);
} else if (context.onreadystatechange) {
context.onreadystatechange();
}
};
};
this.send = function(data) {
const gm_params = ['url', 'headers', 'data', 'responseType', 'cookie', 'binary', 'nocache', 'revalidate', 'timeout', 'context', 'overrideMimeType', 'anonymous', 'fetch', 'password'];
this.data = !this.anonymous || data ? data : undefined;
let standard_params = {
method: this.type,
//jQuery uses username, Tampermonkey uses user
user: this.username,
onload: this.onresponse("onload"),
onerror: this.onresponse("onerror"),
onprogress: this.onresponse("onprogress"),
};
let send_data = Object.assign(standard_params, ...gm_params.map((key) => (this[key] !== undefined ? {[key]: this[key]} : {})));
return GM.xmlHttpRequest(send_data);
};
};
JSPLib.debug.addModuleLogs('network', ['ajax']);
//Auxiliary functions
async function GetFullImageURL(image_url) {
for (let i = 0; i < HANDLED_IMAGES.length; i++) {
let config = HANDLED_IMAGES[i];
let match = XRegExp.exec(image_url, config.regex);
if (!match) continue;
let image_data = await config.handler(match);
return image_data;
}
return null;
}
async function GetFullImageURLs(page_url) {
for (let i = 0; i < HANDLED_PAGES.length; i++) {
let config = HANDLED_PAGES[i];
let match = XRegExp.exec(page_url, config.regex);
if (!match) continue;
let image_data = await config.handler(match);
return image_data;
}
return null;
}
function GetRelatedTagData(tags, profile_urls) {
let promise_array = [];
promise_array.push(QueryArtistUrlData(profile_urls));
if (tags.length) {
promise_array.push(QueryWikiData(tags));
} else {
promise_array.push(Promise.resolve([]));
}
let valid_tags = tags.filter((tag) => tag.match(/^[\u0020-\u0024\u0026-\u0029\u002B\u002D-\u007F]+$/)).map((tag) => tag.toLowerCase());
if (valid_tags.length) {
promise_array.push(QueryTagData(valid_tags));
} else {
promise_array.push(Promise.resolve([]));
}
return promise_array;
}
function GetBufferChecksum(blob) {
return new Promise((resolve)=>{
let fileReader = new FileReader();
fileReader.onload = function(event){
let arrayBuffer = event.target.result;
let wordArray = CryptoJS.lib.WordArray.create(arrayBuffer);
let md5 = CryptoJS.MD5(wordArray).toString();
resolve(md5);
};
fileReader.readAsArrayBuffer(blob);
});
}
function InitializeRelatedTags(tags, profile_urls) {
Promise.all(GetRelatedTagData(tags, profile_urls)).then(([artist_urls, wikis, tags])=>{
let artist_names = JSPLib.utility.arrayUnique(artist_urls.map((artist_url) => artist_url.artist.name));
let other_tags = {};
wikis.forEach((wiki)=>{
other_tags[wiki.title] = (wiki.tag && wiki.tag.category) || 0;
});
tags.forEach((tag)=>{
other_tags[tag.name] = tag.category;
});
$('#related-tags-container .source-related-tags-columns').replaceWith(RenderTranslatedTagsColumn(artist_names, other_tags));
Danbooru.RelatedTag.update_selected();
});
}
function ErrorHandler(error, func_name, image_url, message) {
let process_error = JSPLib.network.processError(error, `ProxyImageDownload-${func_name}`);
let error_key = `ProxyImageDownload-${func_name}-${image_url}`;
JSPLib.network.logError(error_key, process_error);
JSPLib.network.notifyError(process_error, message);
}
function PublicSubmitRequest(...args) {
window.jQuery = PU.public_jquery;
let data = JSPLib.danbooru.submitRequest(...args);
window.jQuery = PU.private_jquery;
return data;
}
//Render functions
function RenderBatchDisplay(image_data) {
return image_data.map((url_data)=>(`
<article class="pu-image-preview" data-id="${url_data.id}" data-order="${url_data.order}">
<a>
<img></img>
</a>
<div class="pu-image-download-progress"></div>
<div class="pu-image-upload-progress"></div>
</article>`)).join("");
}
function RenderTagSelector(name, category) {
return JSPLib.utility.regexReplace(TAG_SELECTOR, {
NAME: name,
ENCODED_NAME: JSPLib.utility.fixedEncodeURIComponent(name),
CATEGORY: category,
});
}
function RenderTranslatedTagsColumn(artist_names, other_tags) {
let html = '';
artist_names.forEach((name)=>{
html += RenderTagSelector(name, '1');
});
for (let name in other_tags) {
let category = String(other_tags[name]);
html += RenderTagSelector(name, category);
}
return JSPLib.utility.sprintf(RELATED_TAG_COLUMN, html);
}
//Site functions
async function GetPixivData(match) {
let illust_promise = JSPLib.network.getJSON(`https://www.pixiv.net/ajax/illust/${match.groups.id}`)
.then((data)=>data, (resp)=>resp.responseJSON);
let resp = await illust_promise;
if (resp.error) {
console.warn("Pixiv API illust error:", resp.message);
return null;
}
let artwork_data = resp.body;
let illust_key = 'pixiv-illust-' + match.groups.id;
JSPLib.storage.saveData(illust_key, artwork_data, API_STORAGE);
PU.api_keys.push(illust_key);
let image_order = parseInt(match.groups.order);
var full_urls, preview_urls;
if (artwork_data.pageCount > 1 && (Number.isNaN(image_order) || image_order > 0)) {
let pages_promise = JSPLib.network.getJSON(`https://www.pixiv.net/ajax/illust/${match.groups.id}/pages`)
.then((data)=>data, (resp)=>resp.responseJSON);
let resp = await pages_promise;
if (resp.error) {
console.warn("Pixiv API illust error:", resp.message);
return null;
}
let pages_data = resp.body;
let pages_key = 'pixiv-pages-' + match.groups.id;
JSPLib.storage.saveData(pages_key, pages_data, API_STORAGE);
PU.api_keys.push(illust_key);
full_urls = pages_data.map((url_data) => url_data.urls.original);
preview_urls = pages_data.map((url_data) => url_data.urls.small);
} else {
full_urls = [artwork_data.urls.original];
preview_urls = [artwork_data.urls.small];
}
if (match.groups.order) {
return {full_url: full_urls[image_order], preview_url: preview_urls[image_order], order: 0, id: match.groups.id};
} else {
return full_urls.map((full_url, i) => ({full_url, preview_url: preview_urls[i], order: i, id: match.groups.id}));
}
}
async function LoadPixivData(api_keys, match) {
let illust_key = api_keys.find((key) => key.startsWith('pixiv-illust'));
if (!illust_key) return;
let illust_data = await JSPLib.storage.retrieveData(illust_key, false, API_STORAGE);
if (!illust_data) return;
let index = parseInt(match.groups.stem.match(/\d+$/)[0]);
var source = "";
if (index === 0) {
source = illust_data.urls.original;
} else {
let pages_key = api_keys.find((key) => key.startsWith('pixiv-pages'));
if (pages_key) {
let pages_data = await JSPLib.storage.retrieveData(pages_key, false, API_STORAGE);
if (pages_data) {
source = pages_data[index].urls.original;
}
}
}
setTimeout(()=>{
$('#post_source').val(source);
}, JSPLib.utility.one_second * 1);
let title = illust_data.title;
let description = (!illust_data.description ? "" : illust_data?.extraData?.meta?.twitter?.description || "");
if (title || description) {
$('#post_artist_commentary_title').val(title);
$('#post_artist_commentary_description').val(description);
Danbooru.Upload.toggle_commentary();
}
let tags = JSPLib.utility.getObjectAttributes(illust_data.tags.tags, 'tag');
let profile_urls = ['https://www.pixiv.net/users/' + illust_data.userId];
InitializeRelatedTags(tags, profile_urls);
}
//Network functions
async function ProxyImageToLocal(image_url, show_progress) {
let [, ext] = JSPLib.utility.getFileURLNameExt(image_url);
let mime_type = MIME_TYPES[ext];
let domain_name = JSPLib.utility.getDomainName(image_url, 2);
let headers = SITE_HEADERS[domain_name];
let ajax_options = {};
if (headers) {
const beforeSend = function (request) {
for (let key in headers) {
request.setRequestHeader(key, headers[key]);
}
};
ajax_options.beforeSend = beforeSend;
}
var xhr_options;
if (show_progress) {
xhr_options = {
onprogress: show_progress,
};
}
for (let i = 0; i < 3; i++) {
JSPLib.debug.debuglog('ProxyImageToLocal-download-' + i, image_url);
let blob = await JSPLib.network.getData(image_url, {ajax_options, xhr_options});
if (blob) {
JSPLib.debug.debuglog('ProxyImageToLocal-data-' + i, blob);
let image_blob = blob.slice(0, blob.size, mime_type);
return image_blob;
}
ajax_options.cache = false;
JSPLib.debug.debugwarn('ProxyImageToLocal-error-' + i, image_url);
await JSPLib.utility.sleep(JSPLib.utility.one_second);
}
}
function UploadImageToDanbooru(image_blob, filename, show_progress) {
let uploadFormData = new FormData();
uploadFormData.append('upload[files][0]', image_blob, PU.unique_id + '-' + filename);
const ajax_options = {
url : '/uploads.json',
type : "POST",
enctype: 'multipart/form-data',
data : uploadFormData,
cache : false,
contentType : false,
processData : false,
};
if (show_progress) {
ajax_options.xhr = function() {
let xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", show_progress, false);
return xhr;
};
}
return _$.ajax(ajax_options);
}
function QueryArtistUrlData(profile_urls) {
let artist_query = {
search: {
url_lower_array: profile_urls,
},
only: 'artist[name]',
};
return PublicSubmitRequest('artist_urls', artist_query);
}
function QueryWikiData(tags) {
// Add the tags as they are
let other_names = new Set(tags);
// Add the tags with common appelations discarded
tags.forEach((other_name)=>{
for (let regexp of COMMON_HASHTAG_REGEXES) {
let normalized_name = other_name.replace(regexp, "");
if (normalized_name != other_name) {
other_names.add(normalized_name);
break;
}
}
});
let wiki_query = {
search: {
other_names_include_any_lower_array: [...other_names],
is_deleted: false
},
only: 'title,other_names,tag[category]',
};
return PublicSubmitRequest('wiki_pages', wiki_query);
}
function QueryTagData(tags) {
let tag_query = {
search: {
name_lower_comma: tags.join(','),
},
only: 'name,category,post_count',
};
return PublicSubmitRequest('tags', tag_query);
}
// Main execution functions
async function ProxyImageDownloadCheck() {
let url = $('#pu-proxy-url').val() || PU.params.proxy_url || $('#upload_source').val() || PU.params.url;
if (!url) {
JSPLib.notice.error("No URL found.");
return;
}
let image_data = await GetFullImageURL(url);
if (image_data) {
return ProxyImageDownloadSingle(image_data);
}
image_data = await GetFullImageURLs(url);
if (image_data?.length === 1) {
return ProxyImageDownloadSingle(image_data[0]);
} else if (image_data?.length > 1) {
return ProxyImageDownloadBatch(image_data);
}
JSPLib.notice.notice("URL not valid.");
}
function ProxyImageDownloadSingle(image_data) {
function imageProgress($progressbar) {
return function (event) {
if (event.lengthComputable) {
let percent_complete = parseInt(100 * (event.loaded / event.total));
$progressbar.progressbar('option', 'value', percent_complete);
$progressbar.find('.pu-progress-text').text(Math.round(percent_complete) + '%');
}
};
}
_$('#pu-display').html(SINGLE_PROXY_DISPLAY);
let [name, ext] = JSPLib.utility.getFileURLNameExt(image_data.full_url);
let filename = name + '.' + ext;
PU.$download_progress = _$('#pu-download-progress').progressbar({value: 0, max: 100});
PU.$upload_progress = _$('#pu-upload-progress').progressbar({value: 0, max: 100});
_$('#pu-progress').show();
ProxyImageToLocal(image_data.full_url, imageProgress(PU.$download_progress)).then(
async (image_blob)=>{
PU.$download_progress.progressbar('option', 'value', 100);
PU.$download_progress.find('.pu-progress-text').text('100%');
JSPLib.debug.debuglog("Blob:", image_blob);
let blob_url = window.URL.createObjectURL(image_blob);
$('#pu-preview img').attr('src', blob_url);
$('#pu-preview').show();
setTimeout(()=>{$('#pu-menu').get(0).scrollIntoView(true);}, 100);
let md5 = await GetBufferChecksum(image_blob);
let uploads = await PublicSubmitRequest('uploads', {search: {media_assets: {md5}}, only: 'id,media_asset_count,media_assets[id,md5],upload_media_assets[id,media_asset_id]'});
if (uploads.length) {
let upload = uploads[0];
JSPLib.debug.debuglog("Upload:", upload);
JSPLib.notice.notice("Upload found... redirecting.");
window.location.replace('/uploads/' + upload.id);
return;
}
UploadImageToDanbooru(image_blob, filename, imageProgress(PU.$upload_progress)).then(
(upload_data)=>{
JSPLib.debug.debuglog("Upload:", upload_data);
JSPLib.notice.notice("Upload completed... redirecting.");
setTimeout(()=>{window.location.replace('/uploads/' + upload_data.id);}, JSPLib.utility.one_second);
},
(error)=>{
ErrorHandler(error, 'UploadImageToDanbooru', image_data.full_url, "Unable to upload downloaded image to Danbooru.");
},
);
},
(error)=>{
ErrorHandler(error, 'ProxyImageToLocal', image_data.full_url, "Unable to download image from source.");
},
);
}
function ProxyImageDownloadBatch(image_data) {
function imageProgress($preview, type) {
return function (event) {
if (event.lengthComputable) {
let percent_complete = parseInt(100 * (event.loaded / event.total));
$preview.find(`.pu-image-${type}-progress`).progressbar('option', 'value', percent_complete);
}
};
}
_$('#pu-display').html(RenderBatchDisplay(image_data));
let preview_promises = [];
let image_promises = [];
image_data.forEach((url_data)=>{
let $preview = _$(`.pu-image-preview[data-id="${url_data.id}"][data-order="${url_data.order}"]`);
$preview.find('.pu-image-download-progress').progressbar({value: 0, max: 100});
$preview.find('.pu-image-upload-progress').progressbar({value: 0, max: 100});
let preview_promise = ProxyImageToLocal(url_data.preview_url).then((image_blob)=>{
if (!image_blob) return;
let blob_url = window.URL.createObjectURL(image_blob);
$preview.find('img').attr('src', blob_url);
});
preview_promises.push(preview_promise);
let image_promise = ProxyImageToLocal(url_data.full_url, imageProgress($preview, 'download')).then(
async (image_blob)=>{
$preview.find('.pu-image-download-progress').progressbar('option', 'value', 100);
if (!image_blob) {
$preview.addClass('pu-progress-warning');
return;
}
let md5 = await GetBufferChecksum(image_blob);
let uploads = await PublicSubmitRequest('uploads', {search: {media_assets: {md5}}, only: 'id,media_asset_count,media_assets[id,md5],upload_media_assets[id,media_asset_id]'});
if (uploads.length) {
let upload = uploads[0];
if (upload.media_asset_count === 1) {
$preview.find('a').attr('href', `/uploads/${upload.id}`);
} else {
let media_asset = upload.media_assets.find((media_asset) => media_asset.md5 === md5);
let upload_media_asset = upload.upload_media_assets.find((upload_media_asset) => upload_media_asset.media_asset_id === media_asset?.id);
if (upload_media_asset) {
$preview.find('a').attr('href', `/uploads/${upload.id}/assets/${upload_media_asset.id}`);
}
}
$preview.addClass('pu-progress-warning');
$preview.find('.pu-image-upload-progress').progressbar('option', 'value', 100);
return;
}
let [name, ext] = JSPLib.utility.getFileURLNameExt(url_data.full_url);
let filename = name + '.' + ext;
UploadImageToDanbooru(image_blob, filename, imageProgress($preview, 'upload')).then(
(upload_data)=>{
JSPLib.debug.debuglog("Upload:", upload_data);
$preview.find('a').attr('href', `/uploads/${upload_data.id}`);
},
(error)=>{
ErrorHandler(error, 'UploadImageToDanbooru', url_data.full_url, "Unable to upload downloaded image to Danbooru.");
$preview.addClass('pu-progress-error');
},
).always(()=>{
$preview.find('.pu-image-upload-progress').progressbar('option', 'value', 100);
});
},
(error)=>{
ErrorHandler(error, 'ProxyImageToLocal', url_data.full_url, "Unable to download image from source.");
$preview.addClass('pu-progress-error');
},
);
image_promises.push(image_promise);
});
Promise.all(preview_promises).then(()=>{
JSPLib.notice.notice('All previews loaded.');
});
Promise.all(image_promises).then(()=>{
JSPLib.notice.notice('All images loaded.');
});
}
// Event handlers
function ProxyImageDownload(event) {
ProxyImageDownloadCheck().then(()=>{
if (PU.api_keys.length) {
JSPLib.storage.saveData('pu-api-' + PU.unique_id, PU.api_keys);
}
});
event.preventDefault();
}
//Main program
async function Main() {
Object.assign(PU, {
params: JSPLib.utility.parseParams(location.search.slice(1)),
drop_target: $('[data-drop-target]').data('drop-target') || '[data-drop-target]',
unique_id: JSPLib.utility.getUniqueID(),
api_keys: [],
controller: document.body.dataset.controller,
action: document.body.dataset.action,
public_jquery: unsafeWindow.jQuery,
private_jquery: window.jQuery,
});
window._$ = unsafeWindow.jQuery;
window.jQuery.expando = unsafeWindow.jQuery.expando;
JSPLib.network.jQuerySetup();
if (PU.controller === 'uploads' && PU.action === 'new') {
JSPLib.utility.recheckTimer({
check: ()=>JSPLib.utility.isNamespaceBound(PU.drop_target, 'paste', 'danbooru'),
exec: ()=>{
_$(PU.drop_target).off('paste.danbooru');
},
}, 500, JSPLib.utility.one_second * 15);
$('#a-new').append(PROXY_CONTROLS);
$('#pu-proxy-url').val(PU.params.proxy_url);
$('#pu-menu #pu-control a').on(PROGRAM_CLICK, ProxyImageDownload);
JSPLib.utility.setCSSStyle(PROGRAM_CSS, 'program');
} else if (PU.controller === 'uploads' && PU.action === 'show') {
let source = $('#post_source').val();
let match = XRegExp.exec(source, FILENAME_RG);
if (!match) return;
let unique_id = match[1];
JSPLib.storage.retrieveData('pu-api-' + unique_id).then((api_keys)=>{
if (!Array.isArray(api_keys) || api_keys.length === 0) return;
let site = api_keys[0].match(/^[a-z]+/)[0];
if (site === 'pixiv') {
LoadPixivData(api_keys, match);
}
});
}
}
/****Initialization****/
//Variables for debug.js
JSPLib.debug.debug_console = false;
JSPLib.debug.level = JSPLib.debug.INFO;
//Export JSPLib
JSPLib.load.exportData(PROGRAM_NAME, PU, {other_data: {PX_IMAGE_RG, XRegExp, HANDLED_IMAGES, GetFullImageURL, GMXML: GM.xmlHttpRequest, jQuery: window.jQuery, GetPixivData, GetFullImageURLs, API_STORAGE, FILENAME_RG, ProxyImageDownloadBatch, RenderBatchDisplay, ProxyImageToLocal, CryptoJS}});
/****Execution start****/
JSPLib.load.programInitialize(Main, {program_name: PROGRAM_NAME, required_variables: PROGRAM_LOAD_REQUIRED_VARIABLES, optional_selectors: PROGRAM_LOAD_OPTIONAL_SELECTORS});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment